From 85219cdc1d2a1357652672bb0bf6858e9f8d8d41 Mon Sep 17 00:00:00 2001
From: Anders Jensen-Urstad Sveriges officiella statistik regleras av Lagen om den officiella statistiken (SFS 2001:99) och Förordningen om den officiella statistiken (SFS 2001:100). En förändring av Lagen om den officiella statistiken trädde i kraft 2013. Myndigheter som har ansvar för Sveriges officiella statistik har bland annat ansvar för att
§ statistiken är objektiv
§ statistiken dokumenteras
§ statistiken kvalitetsdeklareras
Den officiella statistiken ska utan avgift finnas tillgänglig i elektronisk form. All officiell statistik ska ha beteckningen officiell statistik eller märkas med symbolen
- +Observera att beteckningen eller symbolen inte får användas vid vidarebearbetningar av den officiella statistiken.
Produktionen av officiell statistik följer ett särskilt regelverk som verkar för statistiken kvalitet och generaliserbarhet. Eftersom många delar av Sveriges officiella statistik ingår i EUs statistiksystem följer också den officiella statistiken ”European statistics code of practice”.
Biblioteksstatistiken har under åren publicerats på olika sätt, här finns en länk om du vill nå lite äldre statistik.
diff --git a/static/img/logo-statistics.jpg b/static/img/logo-statistics.jpg deleted file mode 100644 index 1d8491357a0e524726c563fcf123b927df55bf81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2058 zcmbW!XH?S(5&-ajNFV`{)6l^n5aLopNg~YvB2pDGN|9m^5S1o~g1|1YBA|rcC5kA5 zbTASKK_k+|9!eyHRU*xxDBS{>gS&fs@7<@}n|bqT=FOYm8{vp>9N6w?>tG9jKp^16 zW&%PUU=6^;#GqoLFenr%E)J7`%OK#Al5j;S>1{H~N-8^*l~5>EjGl%nT2~!~(lpl6 zH83O)2r3$;<|a6z9-e^PS^^Rm7l%v26%YsooEl0E_qRj%7(l`RRiGRULIWa55Euy( zb^|H^021B2cI)8(1V{u75fy{N#3dv*2b#A7A|NnW1OgTng+Ml^<2L&M1Su+|W@sfQ zP4b1J&&l8}<=lp;@1u3d9%0X8@O}}A;u3Q53W`d*e$mj>(kARSGBzoM zd|b5oA`exUreXJdP9lc+f~P8pfDm=Z5f?M0yEdkmpMU&p-Nw^m(-W`Qbak{%A#KnV z!FNKMWZX`T%UE73;%Qr1>^j(_L-Iz`KLj;I2~UP%H9%k`IeQzi+bam1@hY9@5=h=p zz>?qNb-D;P!zHi9?$~B7@m-kfk@?ZYU>cllNS{F*F^bZ^m-jeuzj!Yhn;$~2UWw=< zH#VehGGhA~sR;(+t(~!B?;T49ER8)>GhUeFSLft)?O@#fxr9)>BTo1LDsxdy#gzb< z!5x406wXgv2_*3Ulok&(lA91EfpcL&tqk7_2Erf`cy|uPAm<8H0X=w&8WCQpI`U?` zbUmBlBJ6;TXwe%u!Voc&zZ#Y2zl=o>xwZ|Z3TKx*J39O|SM8sU#>W_5+WeZxb+GFV z(q>Q1GMuXh(@>86)SO;>IQBmUl^O#=59A5^1y zf?*;Z4>S_$zVu45!^=X=#nb3goKx x7nBd5M;@LQi}p*D@JKQ}7*u4JlbiDUSgjJ* zk-eeNem&TkB#mXcO`?%^j@I$jX;ZwMNhvj=X^$94)f0+jL`V>46v+RRrz)ujvE)7_ zHSm)_%t7e+SnM&i6NfX}kY`N9&nR^BXbZdh9@QW;#~cOEY?02XCp_~{QF!d=LqC;b z>DR 19H*1pI7B9ieD63L>xQMwz?nZ JDJS0soj^E&tAsrZ`48TU>3q;8r?%VNS{!(0&O#U=sv2~?Jl~TB@ z22URfkxM-dEMpz<&Naf`;i6+i z=k=&@Ueuz7M-_I4y!!P@xg7F!u3l}nAY^ lX@Qn8h)8pQxPyXL&gmAUR*ezm zdZEHu?>C{1&ivY;y!aju6{)mJjhH}W>9p!I%*{~$bYH9^-pN t!mX_UkFgb^Eauf^XC+{_;HCt`6NcF*qU(=7?Z4gA&WUmK#f7+)<#LTz*4hU_7& z^;$dn%!6*q=PY83$x$5V=UYBxZOSF!h}vledN7M6f`pjwU@a_z`rhZKM%72BcONT! zdL*=uP;(NSPjmROvPyb05WRdE11p^5$f<+p6p?JcT*#&vpq7}QSAbnzOI<^~mJ8yu zJNqxzpO{!!eFSc^OBQDg5Kgj`jO`P*=R69oNA_tL`oeLIo=Ip&>Hs_WbgAffhyl3U z!e}^+Y6@f%eIK29(u@?UHSV6McnNr7FJnnT(+nKSo!|Mq0`YK$hZ3h4j1V5e>gf#y zck#x#X~xQ~-X;W9qtR|dW|(`6iRN$6dV!G>ww8bcA6oR9s#A^ sAcZe32Lc~PJq1-Z>k*sRWOYM=6aOT~c#4CSI;HmK_T9)%S$qv!-kx&4PJs(Td z2FFr0=yh;T&-)h@8lR-oH#3BXNMa>d%ic3dsClcQDJmgl^~L2b_G+Ul+9o}>Qj6rb zX@8}OE|QJU=P;6cFYM03EY(KvmX*(lv}a7>Ne%{W*Qkb+Rcr})9^XLX#f(imp9P0D z5~kGPQj@CfLMlTuHKiUM(2!aq+(>GN)8eJ_WXhbOm_7}yb<>+$msO%B?&Vb$$BZXZ zy3nMZ4$E{#uxwR{4D)MeSW_}eM6f4{MNH38)Z-@*L|isH!lf)4Q%dcI+#q;iqc2KL z4oRB9(@$xdzTP7ok1{mU(RCrbviWA@`ZCIrEF1l$q+5 1nV2s_P$r)H1p#WiyXv!Epn#4r8tAt zFjoG`o{B>bEUcZqVF)xEfHy79PKx%78`YqnYvuTci5GUxd}U76hbojQ402Y*68@S4 zgBTEtib&;y6D{GmS+s&fhahhcKBG38mphidRPzOHpquawZjc*+vq!?dZJdmt%47cO zS+DS!C5-uYx*|8mrI;Ly`I4jbZ87 q4nBOhJzgF>kex5zX~1~p+|v`Tr1 z0nemIZa#QWo5=HkCWAxdz0rZKWW3DqxPv8GnAF^=T|Hkky1Lj{O>Hbk%c+jtE>2)* z 1*MIy6pni+5yuc44w2Ap)TiJWiIH!@#!fWC;Jp E32^*~wfB>!1*N}y0MT0It^MQ>;YUj`=?!@XpRPYDUWUERHqiz5eZ z3Wd#ZPB9z6!!##EpEe_uN1FE{w63SrRHCnL4fBL=4O7|V%7%2|BnRgiCenC{X!#BK zv76r$dSg#Bp<)kd1vF(3wUU%nlb4kIM}`i~%d`EGgys6h$Oa6%%jKqfF@PAv*7T^- z(qv62(&bpzVOr!$Y>&Zo;j{w-uN+zVZtAq%9GzySdtYP0i;BI~&&Ja){`~whjDUlJ z?0R##^UJbe&uSING#VYu>xNicftOpT%n~>F;-TLYXg6@UE_Ggw^tkvg_kWpfhaW!^ zx!&2;h^B+{g*`=^M33>z@wA@&!GDY^t0S!*IUe-|t&QrGW)|{(mG3?I)%i$y(zh{e z*{CpQ!f*N`OyLSb@?;vCac7TS@A1d po?O~@GUu*%qMkZPthU= zCO}Aw*m32eo)2XirdWL2@DczqLaak+-iG4%=-1KVD%atYKOm^zxmqJRHvqbmLShBo zG0|355-@eJV>32$FoCdn+BrgZQ~&@WQBOx>QyYjYl?lYc%3c_F+};hOvN96}zTs8| zDLYC+EUo0cogo_DDw?L=Hm3Y$Kv5ACAx{A)fE~oun99@6*4{ vl7gJ{|M^`Hcd#c}< z#wHGKuEIbdw4Lf7_SrcqEB_PT-sNu=pnR}<8auLcuz}d^?AZTZ!^KtF9SZWdLH}0` z7ftBK6T3RZ#lg+l6e8^ov3I5UcL+1nf9gBBIotkm$IO%+Vhgc@in>61<@k>-W#yIC z{;Bbs0t+iU$3I$7vj0QU)yn)|Wc`P2zgzye^Y4a0)&Gh6AJYHu{f{tIN?BPz%E8p_ zw|VkX!oc7C3z#{WTA2y_xdd_Ya)L}vcvyLPOt@LWCj1aqeh`?CmD7X|%)`me$qC}- z|2HUkdly$@dsE17C@46a6%>b?n-9VbF*9Z51Dlw!f_Y3pto;03AXXkOkST Uo~pB#;)d21!16)mA#wi zzZx{H>>wJh#=qI*;Nb)Df_S*VATSRPFBkW}gtQ>eF3?E)jmZIG 7IV%^agxBwg|2yV2AWnbX z{WS$_t^PDoQT+*90b|p@I&m>}hnW582&(s2lc}Y#y#)k1zyA)Xf7Dz3Uj~bV!yIA^ zF$J?iOt>MeU~`B$E1xNJ(9NK}n{u0S8uNqxV)&ovE)M3d9>&fPaSN!YP;a0C^v4@2 zhCfs?{--S-mXO~(fjGHXL10!8w 4s}8!nKvygFe=b*lbM~9Y|A*!8vG{-30~Gpyo%~n){*SKz(e+<3@Lvi4 zFS`Cm*MG&pe >6zaS52Ix{Bv-V>S%A($x2N&%jKfATxaQlK@+ zj&eFK000}!?=OsJiKsiY646y&SsHN%{sjgYucl;q7XTpTmX{LO^jtX9_e$3sXnLML z{G}79lWxqhfdj|1O^p0$`z68)^k`E1&~n<~D47Z0uUvzcQ)Ppnxk Fx5KpYp=5X!c|pl|Jv{RzU^53dZn_>LbMD% z8i`s;l<_A6;2!n{B~Tn-4WkKbiTEAOh{&@Br7N1h83OgO-z9NgW*>lpaq2JM1nz z6|Sug;3Ev5EYhn(@5$cY-u3g4q+;bEpzS`)Tb9t>pKa5>D;wez W7R7;N&GQ9tN~5VzL2YyTUS(*<|hEr~Lr#JA_`jZ{*Q7_yD*t zzJxuefbe~~3QeYW2#x}QkjSf;gRSt-f9(5GQS2oLa7o|BvP44#__FfpAiY90LeVt% zl7=Po9b*k2umd+2q;L3QjU+Sz=2;PG;tP7*Z=aiE`~bElq4(Ge-RzG6R8(83aA&xH zJYZ{)FKS?gT2~UF2&M|PbsL6P=#Pa|LgOC-odaD8{Yc55U;;@iKYVy$g?_2_HDo#N z(*n|3_QJwKPeC_*Ag1J4-mc8>AM5%{5Q6>S$Vqh#4Jknh)_`XzCrJ-VlY&0Ue1+`v zF+KXw-JS5iM0*JVjU-4{cHu6^vQ}rJgAG0pys6`#T~|i!Km8{4se!??n;rK1qN9r8 z;d8HAD-}-_{0Uwvqb+MHmp@hDGc1TecZ#6@zm%fd0qvG`I`nt0N%LiT14*Km_50 z%iWjn@-!ZxuC_hr5{A`TlG@U6UV~6ui!|;+)TZ<)PvVv`26p_M$#1ngWCa&s4}U@+ z2w?|^TC9F=RUeMCkN!M(4TvpVMYU47ptTQTvL$5zaLv`p1276!9pG-1)q@w_({;-) zi0TMFa(we51!U){lxbkM+|vc>EHu5(Qc)^Vhty^b*D%)7sEU6o5}8{;bd2Be`gQ2q zXVA9-3A*>+EKqlLjT#s~ zj~hGFlWc5k!k`dFMRlx9KYle5HmZWZg)ZtmcfQqR&97p?U&@2cQd(mRqO|%_T5me; z>S1zrINjDJ&`YO-A2=F*E&w3=Joe!QDA>^EMr=Gh`t#juw2E&Rwl=6#M;d69ep6Sf zC~S^sTjwFC)Dkb`Qh4rm$l&I;L3m=Mi{bCrC ;2t0~NDBL0i?xB uoocx6m^~B-Tq_M-$1M&KQYUaT zyv*x~@VIlwJb-JlTg2H(k(zfN-%rgMxES(>uTQ*gt&(zWUfQ%wplb-NAT?1;XxZo^ zAH5npdS%bQBh2G|9#aipcfT0ArvnI9pr$0yINL>Zfn9YD;oozN*>V#Ql*K$81>DeR zn!PUITXtU6!)fGr-G1c&aGy{zw_@lXdKTwDSzS?S!i+dtM-N3z8ZrBFe5<53KVE}S z|Nd(ESTES;WOuscImp8|d--|%#aqK#gbjH{SqXlCn^5Lgv-*XusuJ=pqA3D6k6fwB z &>)Ayy)(8Ov1732BKM|5qx+};R zQh)|LVd`+>iuYZjiHF`Y3iAX`ivtg;c6QZ6<_$rm!Q0#B#O@p(Gf7|BR5w!8k1%?0 zC48w$Y*EM=w#hzJRbA+02}(fDdEaPhbx}&iUmmX-Ha0;|zY@~Y2sY1Wrlum(($e$+ zXn^mU`ud%&n}c!n1qI^J4G@Qyscvp={-&lyXumd5R<9$HQ95y$Oc*Jet~#8dw_+KX z4HA(FdwY8`+}zw>f%3DDC8_P-k1DzAva#SFk8RP{_kq&1<4%Nr^ zdVPaMO---U2O|@FBqc*E9UKy*MMJTg)Q8!zN0%f_TPg&vKi1R`s1IwOPav!yIP82x zm{cpnDwJ`^@90 `!s+QQh|Z`QV=#`%6?LuxhvM^Vf|i0Xl@j$71 D0&BP=f016ILvSAE8jfVsDfQHhd39b02g$dw&fLR*;j>J7@!RCf zusZ>m4rF}%VdXt~HJ?KgeY@h@FR0(yjIKo+0Jt;8YF!)oak6j=!s8$+W0hGwmQW$M zHHm(BV(}0NDsC=yXk>%P(66+8)kuCs!aAEwn1eJ&jD(pZAIr$no{R%c_nR+~1_P)5 zCY}Ot>nUd5a)K_5x@=4+`)!dsp7QsiBb@7tz=aHBo#Us)TKed->M$e=-zMF--cF8% zE@6gI(^?2&{XleIp~IF(WIU4Z3#JGrv22E_7}G`{`q7F)5Ec&0arC@wLnu}!u5}{! z=ZB3((U3-Gst#QQ!^vGVWrkWi<^ly)yQV;?Y|;1}o47t;DPhYq0nA(iTMm{J+x4kC zJqoi?YA?UTt729FerEaV%`x?6D`_9|Q5X8!E;Npf)8TilLxtuD>cv~u)xJIYtK}m! zWb&Q%@DWbR)(7L}rH^}o4k>MJYPm8~Eo~Qj#B3LtT=C1$OsQ6x0{QmP%+l901X#y! z8wq}NrikyKL=k5YZKia3Un<~q2j~Z1w`J>lfc=#6YB$d yoyqqp-a=DR+NxBKdI4 zWxN>%KQ0n6eMl;2H#!ZUYb1h}&@U#ZkrwXol`i`+v1T#vsNrn`mC_5)yFGmOn4rXy z>v*1H=d>A6^}wm?=RgCpqN3*)M)g>Qgx!fF%=Dy0v!XO tb`bNN=zw zI}#d0g<0+1j@Ibdy!sL_j@aOFyb_!~F3}wDBCtqjW1Jzb?hepJ(kn)Q(>s?%oAV~! z3}--$f-5~sB^-r}$R_E^72%9ba0Y%2OfB^q0x6Njy>~mj+nj6KvH9XpAqO_A nqvofn@UzEKR*lB2SK*2#|R^MxDfaurz;JlF5 z`Y>>l9C~P@mO-d1V<1O`W@T(Vx%lTW+;ZV)NFR;_IMKWVt%pM*@+o)cH4-(#_lGSh zdvZVAq m@}yY&bg@ec7Fd&O`t2(vN+je6LG3|E_6)t?7qW5j z1vlBS8#xSKG6kH$_M(B%(lP-h7EZYJ)y;RMG4lKTeH$7q$Z!R%<9^IuuRc @e)LGnMSM(230TwrDso2<4Qblr*BBu`& zk~j7*CSG_ZOics$AqMsH2h_W|$NCb}*~m&VFb4F8N?)AIm+(NBQ{g_vsr#r-GCyR; z{8T*lC;0K&$h0tbK&~&%YK|t$#tkoVi&IB4b8c-8Ji;Gqcwg9JZ6~>>!wZ#fyiRZM z&zl}R{jdvzWxA%c&a*X>1$)oah5qgVpD^2VESSH~iCVtBl;aoA!s4PIP?K)=tIa!( zdlpkr!A?+Nt-0$tzE+mI`VzMZC&BK$=c#>W!?^4detcT zPX}5zgD*9&s_(n Sp6;0Rry3|>+ig8@(|l&IfB8s=3Npf60}$0?SmYs}T9fMx1JGhV}1g;auV zC2Uf`QreokRYPW+R^=xOW1p0em^E)9S3b%%HXq`!oamO{_f>GIy)w3V;i9a-pGTXA zU))=bSUY^)(W11TfxU*~s9pR+Vz3zT@-rc0$r5U7#;__uofq4#FA3)t9xL hlMrt%5J>(B67J?iGUz`;+%Mqn;OzgS;cLM+i>DMQ2+ z^F9H_!F?iSZE($lh-^f9f-PN@52|P06;*b|jGqqH7DXq|!XH~%+YsQ$d)9Amw|IoS;^VB;aL%%IPY+bqHmmQ%TSuKg zJsbgM)rQjufuin(6PW6d_mw3@!HY`q6eDgr>gvOAxN=&fYVqHf^9yxnK7X=pOg(F* zDpo~ug25hLW=&3#A09=aUSTZ^os)qBh1=%B#GCI_(Lfi@%fwN0+2lc3z@Y?^L8iY? z^{$Pi4)47eYR08Og7MP>gf8XQEqJ@Nn5gtfanx)cxBLgv?4TV)sVyqUl4V>h^tN5> z0h{U5OP1R=Vz=$)FDDo@Dl{{W?^VTbDco-{FIR;aC}$QT!2!-CU9LAfhvvy}ash zF7lMAFgFn;1srC%3KB#jFpkiq<_+}EDMpW1E&VH)o*?z%3V=$1(l d6?R{Wz-(?t9$hF@(zY7)*qb?Gm*!7Y6W>hA?+}&Uf5wvo5rrv6d1su z>L4QR%;g lwnM9_ZNdY$mQ}~;Mx?-q2=%jT| zV_xQd_=vmN74t+X*>~38)@H|AO2j+G25Ttmub#<4oH!yz3FLLfuBFFb$=njhsd>?( z4C~S)@`3hT!SO+E)VD {wq#1wZU4rrucY+%c+Hobk0!@`|;mpQD55<8jeA~CXW z5X7&;qI4Kg8lj}!#t*TDMw<{=(5WO$LRlLR(0i^#r(@4iNJ>{#Zt+p}h(z)nH@()g zDONO8Q)Nz50$1x(Xql3&kO}vPjOQ4uzGOfe^a-W;`Ze-a_~ysyB2nRJkY~SRzrLV= zz#xIKui;B%7uXRDAHa>9mshLo*e)Vk_ESXKekr|YJY{2Z^Aq}MA7X@M^Hd1}E*kHv z<)M)``$FUQSu+$CViEKsHVnU$+Jq(8Kpu tyG|r283^&ri zI(_~LXF?x;h&g)OWS%Bd?A=Qbq2HzBiQ}ax#OhTe3cr@tRcOV%H{i z@6$$ZO?bf&We(}r%2#D@A@|Fd~uOLk7EL=ZMyg;HgCSoYb zme2e-@0~#@6ZpkS3Yuq5tIUpOl5z_e1J@8`K#8_W4Hdui6z;p8K@6GPw8c4PXmZq2 z8XQLsJ!D%o9k(Qb5nzp#bvmoEX%o8o3`H5vrX&5WH^WWq0j$@pDY+%l;K0CgcN=*< zPWq|{KjIbM{4;iQ8eU}390|Jglo;`>B9 >xTD9{}GpvQMn`F0Iwd2)PYsvFl^gDl&dI<;8#(t=*|y3#$`1 z7Le}lQxz~gKvXjjQM9>7EL9;xj*sYeBKjE!v(rK==9F?K9E&aWjx(dQ=^jJV ^_T+V {K7JYA+b-WPec@Mj7|r zah=iRQUsoDDuoXZq*vz@A?7)rYlYw4)3FYJ6l|(>m$vU6DxoV`*FsIwhp3fZNAMT< z&}quYv>^rCqv&wGoYe>Tyj;z}ahy&XV%@~@*cW@>AcVo?tI9m~Wwm__G-0<$zoeyS ziy%SLOCl8^txJrAo&Cn7Yn+a5`7m ?xyiMDu&WR; sQEV*uWG%e9GZ`0o6FEpUcfT3%`BTtPcYNy zvzR3w8Y9RgC`;cc;OLTm)D!42GG>HuNMjXYR9o;cY!&%Lj|jr##(RE6^9!Af6kI`F z 0T>i$sTX~%@2NkTHP=dx){Ks z)806))Cu*I{D7DEaGK1% kmWVOAh1BRQtpnqlX(8}A|8YokAN$@kRnC_)2oa0*;$q{r|eEH*GKMRgHQ z)#Lhy_MC@GYb^JlEUQi`HSbF< g0X+H>yRSUYo%7=l5Oc z7D!O$QT-B{wbXn461tUJ0~s6kF=hxVSSR8MnoZVW9ADbJf3n&lT|j0^woSv@Fwp%T zK3lPXc-530k@(fbu-quvo%5@*YS|>$(3Jxx@9Uc`(Qu@L_u?@(Vy{te+Oal1E+bFU z-JeCN@zaM`UEc%DJ#()KhogDd)dxT4$@r(Ag^hTe14k_^{Rt?jJ%y2J@6KP}bp>$V zB1A}pS^C2pm11pB4D)Xg4LrP7mTI({qhxAxqAd5#jDx;_De1pqhEb5H?vFTZrLVHu zKS~lJ@Qx@@*5!(E8#P<1&k)W}LviT&md=!i%6NY>R8jz*sm}?%jUZK5a zC*2)i{G9`6qD1B{E%!rtdAjmX6I;I2u6teUW7sqmN8C18VhSny=)voBR1* C%v5YPfm#Qia}t`k>)&zL25!w_H{yP8yc2~#O#gHy z5Zb`NyL;pc*|Vw2R8;CSYtzO6XqL{Gs)%fbmz5J)JBLqCO(Cg6S~3-FyT}otg&ddW zAnv%pW`Y1T2tu(g$b26uZ%gHtk<~V+x8Td7#?5-|&n)l979p1{QQ8m&h=)8?EawB$ zJXco_+P?MnQX~6g7rZhnusxNpw8fCIALLmLF0PbB`ywLsa0y3N=+qS?-y#8--M`QJ zE;(&YPDUo8_lazxD^7(WZ_tyq)1O(cO&EV0B!--y@$&nFreHIgen~uebVv?1MZxED zGnv*SXGcc{JPR5jAyv%P9Mq=oQ%!0~dI{l{ $UHaro;xWpE0chHj}E-5}}72lC%XC z5lqMXgh^gi5^sD(^0Tj*^Cje axRNi9O5Tr9Vmif(E#39jrip-DX zL?;$F(95dS7AOq#uq45rJ_4D#vN$y&(Hy4=eS)y^!HHf|Iq#hs#%abc*Sl#Yx)eA4 gW9X4ot>5PA2pN20`f&{qdZq@DmsXLgkuVDWKSj(`761SM literal 0 HcmV?d00001 From 9142d867f46a6083658abf5142212cc64a61fa48 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 22:17:19 +0000 Subject: [PATCH 11/18] Bump django from 3.2.18 to 3.2.19 Bumps [django](https://github.com/django/django) from 3.2.18 to 3.2.19. - [Commits](https://github.com/django/django/compare/3.2.18...3.2.19) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f14f8d2..41356a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ blinker==1.5 django-excel-response==1.0 git+https://github.com/ierror/django-js-reverse@7cab78c#egg=django-js-reverse django-mongoengine==0.5.4 -Django==3.2.18 +Django==3.2.19 pymongo==3.12 mongoengine==0.24.2 openpyxl==3.0.10 From 03de5103339fc41812295c0a7c263d3cb1aef1c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 03:53:41 +0000 Subject: [PATCH 12/18] Bump requests from 2.27.1 to 2.31.0 Bumps [requests](https://github.com/psf/requests) from 2.27.1 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.27.1...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f14f8d2..ed2cf15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ pymongo==3.12 mongoengine==0.24.2 openpyxl==3.0.10 pytz==2022.1 -requests==2.27.1 +requests==2.31.0 xlrd==1.2.0 xlwt==1.3.0 urllib3==1.26.11 From f0d7b98ef69b600ab602f1bbecd78e0f7193021f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jul 2023 23:28:15 +0000 Subject: [PATCH 13/18] Bump django from 3.2.19 to 3.2.20 Bumps [django](https://github.com/django/django) from 3.2.19 to 3.2.20. - [Commits](https://github.com/django/django/compare/3.2.19...3.2.20) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f1f48ba..291b7af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ blinker==1.5 django-excel-response==1.0 git+https://github.com/ierror/django-js-reverse@7cab78c#egg=django-js-reverse django-mongoengine==0.5.4 -Django==3.2.19 +Django==3.2.20 pymongo==3.12 mongoengine==0.24.2 openpyxl==3.0.10 From 5ed857cf3a616f375a0d601e7b602dcf9ea83ca7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jul 2023 21:17:12 +0000 Subject: [PATCH 14/18] Bump certifi from 2022.12.7 to 2023.7.22 Bumps [certifi](https://github.com/certifi/python-certifi) from 2022.12.7 to 2023.7.22. - [Commits](https://github.com/certifi/python-certifi/compare/2022.12.07...2023.07.22) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 291b7af..6ebb6af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,6 @@ requests==2.31.0 xlrd==1.2.0 xlwt==1.3.0 urllib3==1.26.11 -certifi==2022.12.7 +certifi==2023.7.22 black==22.6.0 gunicorn==20.1.0 From e986180eb8c1b9b2a7b07da98fbcbc0ad8acc01d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 01:43:40 +0000 Subject: [PATCH 15/18] Bump urllib3 from 1.26.11 to 1.26.17 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.11 to 1.26.17. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.11...1.26.17) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ebb6af..b85bd14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ pytz==2022.1 requests==2.31.0 xlrd==1.2.0 xlwt==1.3.0 -urllib3==1.26.11 +urllib3==1.26.17 certifi==2023.7.22 black==22.6.0 gunicorn==20.1.0 From 7ad03051677770a7ee77ceea26b0043545ab7552 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 01:16:46 +0000 Subject: [PATCH 16/18] Bump urllib3 from 1.26.17 to 1.26.18 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.17 to 1.26.18. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.17...1.26.18) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b85bd14..29afa48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ pytz==2022.1 requests==2.31.0 xlrd==1.2.0 xlwt==1.3.0 -urllib3==1.26.17 +urllib3==1.26.18 certifi==2023.7.22 black==22.6.0 gunicorn==20.1.0 From cb2bc0ddb8765ae1144378bafcb616b03c9bad92 Mon Sep 17 00:00:00 2001 From: Anders Jensen-Urstad Date: Wed, 25 Oct 2023 18:09:41 +0200 Subject: [PATCH 17/18] Add more logging --- libstat/forms/survey.py | 8 ++++---- libstat/models.py | 12 +++++++++++- libstat/utils.py | 25 ++++++++++++++++++++++++- libstat/views/dispatches.py | 4 ++++ libstat/views/survey.py | 25 ++++++++++++++++++++----- libstat/views/surveys.py | 9 +++++++++ requirements.txt | 1 + 7 files changed, 73 insertions(+), 11 deletions(-) diff --git a/libstat/forms/survey.py b/libstat/forms/survey.py index 26aa687..00d5c05 100644 --- a/libstat/forms/survey.py +++ b/libstat/forms/survey.py @@ -187,10 +187,10 @@ def _cell_to_input_field(self, cell, observation, authenticated, variable_type): if isinstance(field.initial, str): field.initial = field.initial.strip() - if cell.variable_key == "Besok01": - logger.debug("attrs:") - for attr, value in list(attrs.items()): - logger.debug(attr) + #if cell.variable_key == "Besok01": + # logger.debug("attrs:") + # for attr, value in list(attrs.items()): + # logger.debug(attr) return field diff --git a/libstat/models.py b/libstat/models.py index 4dbec54..772f6a4 100644 --- a/libstat/models.py +++ b/libstat/models.py @@ -546,7 +546,17 @@ def is_published(self): @property def latest_version_published(self): - return self.published_at is not None and self.published_at >= self.date_modified + # It can happen that self.modified_at is a timezone-unaware datetime object + # causing an error when compared with self.published_at ("can't compare offset-naive + # and offset-aware datetimes"). datetime.utcnow() is used in various places and does + # *not* set tzinfo. We shouldn't have these kinds of problems, but I'd rather not + # start poking at date/time handling given the state of the code, so let's just work + # around it here. + modified_at_with_tz = self.date_modified + if self.published_at is not None and self.published_at.tzinfo: + if not self.date_modified.tzinfo: + modified_at_with_tz = modified_at_with_tz.replace(tzinfo=self.published_at.tzinfo) + return self.published_at is not None and self.published_at >= modified_at_with_tz def target_group__desc(self): return targetGroups[self.target_group] diff --git a/libstat/utils.py b/libstat/utils.py index 27c7d53..d8be74e 100644 --- a/libstat/utils.py +++ b/libstat/utils.py @@ -1,6 +1,7 @@ -# -*- coding: UTF-8 -*- import datetime +from ipware import IpWare + ALL_TARGET_GROUPS_label = "Samtliga bibliotek" SURVEY_TARGET_GROUPS = ( ("natbib", "Nationalbibliotek"), @@ -68,6 +69,8 @@ ISO8601_utc_format = "%Y-%m-%dT%H:%M:%S.%fZ" +ipw = IpWare() + def parse_datetime_from_isodate_str(date_str): # Note: Timezone designator not supported ("+01:00"). @@ -95,3 +98,23 @@ def parse_datetime(date_str, date_format): return datetime.datetime.strptime(date_str, date_format) except ValueError: return None + + +def get_ip_for_logging(request): + # We use ipware (ipw) tp easily get the "real" client IP. + # Might need some adjustments depending on the environment, e.g. specifying + # trusted proxies. + # This is *ONLY* for logging purposes. + ip, trusted_route = ipw.get_client_ip(request.META) + return ip + + +def get_log_prefix(request, survey_id=None, survey_title=None): + log_prefix = f"[IP: {get_ip_for_logging(request)}]" + if request.user.is_superuser: + log_prefix = f"{log_prefix} [ADMIN]" + if survey_id: + log_prefix = f"{log_prefix} [survey: {survey_id}]" + if survey_title: + log_prefix = f"{log_prefix} [{survey_title}]" + return log_prefix diff --git a/libstat/views/dispatches.py b/libstat/views/dispatches.py index 79c49fb..1b9fa7a 100644 --- a/libstat/views/dispatches.py +++ b/libstat/views/dispatches.py @@ -8,6 +8,7 @@ from bibstat import settings from libstat.models import Dispatch, Survey +from libstat.utils import get_log_prefix logger = logging.getLogger(__name__) @@ -35,6 +36,7 @@ def _rendered_template(template, survey): def dispatches(request): if request.method == "POST": survey_ids = request.POST.getlist("survey-response-ids", []) + logger.info(f"{get_log_prefix(request)} Creating dispatches for {survey_ids}") surveys = list(Survey.objects.filter(id__in=survey_ids).exclude("observations")) dispatches = [ @@ -67,6 +69,7 @@ def dispatches(request): def dispatches_delete(request): if request.method == "POST": dispatch_ids = request.POST.getlist("dispatch-ids", []) + logger.info(f"{get_log_prefix(request)} Deleting dispatches {dispatch_ids}") Dispatch.objects.filter(id__in=dispatch_ids).delete() message = "" @@ -83,6 +86,7 @@ def dispatches_delete(request): def dispatches_send(request): if request.method == "POST": dispatch_ids = request.POST.getlist("dispatch-ids", []) + logger.info(f"{get_log_prefix(request)} Sending dispatches {dispatch_ids}") dispatches = Dispatch.objects.filter(id__in=dispatch_ids) dispatches_with_email = [ dispatch for dispatch in dispatches if dispatch.library_email diff --git a/libstat/views/survey.py b/libstat/views/survey.py index 13e30cf..db2c90f 100644 --- a/libstat/views/survey.py +++ b/libstat/views/survey.py @@ -23,6 +23,7 @@ ) from libstat.forms.survey import SurveyForm from libstat.survey_templates import survey_template +from libstat.utils import get_log_prefix logger = logging.getLogger(__name__) @@ -68,7 +69,7 @@ def sigel_survey(request, sigel): return HttpResponseNotFound() -def _save_survey_response_from_form(survey, form): +def _save_survey_response_from_form(survey, form, log_prefix): # Note: all syntax/format validation is done on client side w Bootstrap validator. # All fields are handled as CharFields in the form and casted based on variable.type before saving. # More types can be added when needed. @@ -108,9 +109,13 @@ def _save_survey_response_from_form(survey, form): _f for _f in form.cleaned_data["selected_libraries"].split(" ") if _f ] + logger.info(f"{log_prefix} Saving form; submit_action={submit_action}, survey_status={survey.status}") if submit_action == "submit" and survey.status in ("not_viewed", "initiated"): if not survey.has_conflicts(): + logger.info(f"{log_prefix} submit_action was 'submit' and survey.status {survey.status}; changing status to submitted") survey.status = "submitted" + else: + logger.info(f"{log_prefix} submit_action was 'submit' and survey.status {survey.status}; however status NOT changed to submitted due to conflicts") survey.save(validate=False) @@ -147,8 +152,10 @@ def can_view_survey(survey): return HttpResponseNotFound() survey = survey[0] + log_prefix = get_log_prefix(request, survey_id, survey) if not survey.is_active and not request.user.is_authenticated: + logger.info(f"{log_prefix} tried to {request.method} but survey is not active and user is not authenticated") return HttpResponseForbidden() context = { @@ -164,6 +171,7 @@ def can_view_survey(survey): if can_view_survey(survey): if not request.user.is_authenticated and survey.status == "not_viewed": + logger.info(f"{log_prefix} User not authenticated and survey.status was not_viewed; setting survey.status to initiated and saving") survey.status = "initiated" survey.save(validate=False) @@ -174,7 +182,7 @@ def can_view_survey(survey): and not request.user.is_superuser ): logger.error( - f"Refusing to save because survey has status submitted (or higher), library {survey.library.sigel}" + f"{log_prefix} Refusing to save because survey has status submitted (or higher), library {survey.library.sigel}" ) return HttpResponse( json.dumps({"error": "Survey already submitted"}), @@ -182,12 +190,15 @@ def can_view_survey(survey): content_type="application/json", ) else: + logger.info(f"{log_prefix} POSTing survey") form = SurveyForm(request.POST, survey=survey) errors = _save_survey_response_from_form( - survey, form + survey, form, log_prefix ) # Errors returned as separate json - logger.debug("ERRORS: ") - logger.debug(json.dumps(errors)) + if errors: + logger.info(f"{log_prefix} Errors when saving survey: {json.dumps(errors)}") + else: + logger.info(f"{log_prefix} Survey saved") return HttpResponse(json.dumps(errors), content_type="application/json") else: @@ -232,6 +243,8 @@ def release_survey_lock(request, survey_id): def survey_status(request, survey_id): if request.method == "POST": survey = Survey.objects.get(pk=survey_id) + log_prefix = f"{get_log_prefix(request, survey_id, survey)}" + logger.info(f"{log_prefix} Changing selected_status from {survey.status} to {request.POST['selected_status']}") survey.status = request.POST["selected_status"] survey.save() @@ -242,6 +255,8 @@ def survey_status(request, survey_id): def survey_notes(request, survey_id): if request.method == "POST": survey = Survey.objects.get(pk=survey_id) + log_prefix = f"{get_log_prefix(request, survey_id, survey)}" + logger.info(f"{log_prefix} Adding notes to survey") survey.notes = request.POST["notes"] survey.save() diff --git a/libstat/views/surveys.py b/libstat/views/surveys.py index e59af88..f668ad3 100644 --- a/libstat/views/surveys.py +++ b/libstat/views/surveys.py @@ -33,6 +33,7 @@ ) from libstat.survey_templates import survey_template from data.municipalities import municipalities +from libstat.utils import get_log_prefix logger = logging.getLogger(__name__) @@ -147,6 +148,7 @@ def surveys(request, *args, **kwargs): def surveys_activate(request): if request.method == "POST": survey_ids = request.POST.getlist("survey-response-ids", []) + logger.info(f"{get_log_prefix(request)} Activating surveys {survey_ids}") Survey.objects.filter(pk__in=survey_ids).update(set__is_active=True) request.session["message"] = "Aktiverade {} stycken enkäter.".format( len(survey_ids) @@ -158,6 +160,7 @@ def surveys_activate(request): def surveys_inactivate(request): if request.method == "POST": survey_ids = request.POST.getlist("survey-response-ids", []) + logger.info(f"{get_log_prefix(request)} Inactivating surveys {survey_ids}") Survey.objects.filter(pk__in=survey_ids).update(set__is_active=False) request.session["message"] = "Inaktiverade {} stycken enkäter.".format( len(survey_ids) @@ -318,8 +321,10 @@ def surveys_overview(request, sample_year): @permission_required("is_superuser", login_url="index") def surveys_statuses(request): + log_prefix = f"{get_log_prefix(request)}" status = request.POST.get("new_status", "") survey_response_ids = request.POST.getlist("survey-response-ids", []) + logger.info(f"{log_prefix} Changing status for {survey_response_ids}") if status == "published": num_successful_published = 0 for survey in Survey.objects.filter(id__in=survey_response_ids): @@ -333,6 +338,9 @@ def surveys_statuses(request): "de svarar för några bibliotek eller för att flera enkäter svarar för " "samma bibliotek. Alternativt saknar biblioteken kommunkod eller huvudman." ).format(message, len(survey_response_ids) - num_successful_published) + logger.info(f"{log_prefix} Published {num_successful_published} survey(s); failed publishing {len(survey_response_ids) - num_successful_published} survey(s)") + else: + logger.info(f"{log_prefix} Published {num_successful_published} survey(s)") else: surveys = Survey.objects.filter(id__in=survey_response_ids) for survey in surveys.filter(_status="published"): @@ -343,6 +351,7 @@ def surveys_statuses(request): message = "Ändrade status på {} stycken enkäter.".format( len(survey_response_ids) ) + logger.info(f"{log_prefix} Changed survey.status to {status} for {len(survey_response_ids)} surveys") request.session["message"] = message return _surveys_redirect(request) diff --git a/requirements.txt b/requirements.txt index 29afa48..8b129f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,4 @@ urllib3==1.26.18 certifi==2023.7.22 black==22.6.0 gunicorn==20.1.0 +python-ipware==1.0.5 From b2b301b5386626b1a0ae73b0c8c5e7addf652870 Mon Sep 17 00:00:00 2001 From: Anders Jensen-Urstad Date: Fri, 27 Oct 2023 11:28:11 +0200 Subject: [PATCH 18/18] Bump version number --- bibstat/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bibstat/settings.py b/bibstat/settings.py index 75278fb..e36183c 100644 --- a/bibstat/settings.py +++ b/bibstat/settings.py @@ -17,7 +17,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent # Bibstat version number - update this when making a new release -RELEASE_VERSION = "1.20.2" +RELEASE_VERSION = "1.20.3" """ ----------------------------------------------------------