Mise à jour de février (#581)

Co-authored-by: Thomas Girod <thgirod@hotmail.com>
Co-authored-by: Julien Constant <julienconstant190@gmail.com>
Co-authored-by: Skia <skia@hya.sk>
This commit is contained in:
authored and GitHub committed 2023-03-09 13:39:33 +01:00
1 parent b7f20fed6c
commit dd3ad42eb5
38 files changed
+1432 -1072

No files matched your search

+35
View File
@@ -0,0 +1,35 @@
from core.models import Page
class FourDigitYearConverter:
regex = "[0-9]{4}"
def to_python(self, value):
return int(value)
def to_url(self, value):
return str(value).zfill(4)
class TwoDigitMonthConverter:
regex = "[0-9]{2}"
def to_python(self, value):
return int(value)
def to_url(self, value):
return str(value).zfill(2)
class BooleanStringConverter:
"""
Converter whose regex match either True or False
"""
regex = r"(True)|(False)"
def to_python(self, value):
return str(value) == "True"
def to_url(self, value):
return str(value)
-1
View File
@@ -1 +0,0 @@
var Vue=function(e){"use strict";function t(e,t){const n=Object.create(null),o=e.split(",");for(let r=0;r<o.length;r++)n[o[r]]=!0;return t?e=>!!n[e.toLowerCase()]:e=>!!n[e]}const n=t("Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt"),o=t("itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly");function r(e){return!!e||""===e}function s(e){if(N(e)){const t={};for(let n=0;n<e.length;n++){const o=e[n],r=A(o)?c(o):s(o);if(r)for(const e in r)t[e]=r[e]}return t}return A(e)||M(e)?e:void 0}const i=/;(?![^(]*\))/g,l=/:(.+)/;function c(e){const t={};return e.split(i).forEach((e=>{if(e){const n=e.split(l);n.length>1&&(t[n[0].trim()]=n[1].trim())}})),t}function a(e){let t="";if(A(e))t=e;else if(N(e))for(let n=0;n<e.length;n++){const o=a(e[n]);o&&(t+=o+" ")}else if(M(e))for(const n in e)e[n]&&(t+=n+" ");return t.trim()}const u=t("html,body,base,head,link,meta,style,title,address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot"),p=t("svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view"),f=t("area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr");function d(e,t){if(e===t)return!0;let n=R(e),o=R(t);if(n||o)return!(!n||!o)&&e.getTime()===t.getTime();if(n=N(e),o=N(t),n||o)return!(!n||!o)&&function(e,t){if(e.length!==t.length)return!1;let n=!0;for(let o=0;n&&o<e.length;o++)n=d(e[o],t[o]);return n}(e,t);if(n=M(e),o=M(t),n||o){if(!n||!o)return!1;if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e){const o=e.hasOwnProperty(n),r=t.hasOwnProperty(n);if(o&&!r||!o&&r||!d(e[n],t[n]))return!1}}return String(e)===String(t)}function h(e,t){return e.findIndex((e=>d(e,t)))}const m=(e,t)=>t&&t.__v_isRef?m(e,t.value):E(t)?{[`Map(${t.size})`]:[...t.entries()].reduce(((e,[t,n])=>(e[`${t} =>`]=n,e)),{})}:$(t)?{[`Set(${t.size})`]:[...t.values()]}:!M(t)||N(t)||B(t)?t:String(t),g={},v=[],y=()=>{},b=()=>!1,_=/^on[^a-z]/,S=e=>_.test(e),x=e=>e.startsWith("onUpdate:"),C=Object.assign,w=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},k=Object.prototype.hasOwnProperty,T=(e,t)=>k.call(e,t),N=Array.isArray,E=e=>"[object Map]"===I(e),$=e=>"[object Set]"===I(e),R=e=>e instanceof Date,O=e=>"function"==typeof e,A=e=>"string"==typeof e,F=e=>"symbol"==typeof e,M=e=>null!==e&&"object"==typeof e,P=e=>M(e)&&O(e.then)&&O(e.catch),V=Object.prototype.toString,I=e=>V.call(e),B=e=>"[object Object]"===I(e),L=e=>A(e)&&"NaN"!==e&&"-"!==e[0]&&""+parseInt(e,10)===e,j=t(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),U=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},H=/-(\w)/g,D=U((e=>e.replace(H,((e,t)=>t?t.toUpperCase():"")))),W=/\B([A-Z])/g,z=U((e=>e.replace(W,"-$1").toLowerCase())),K=U((e=>e.charAt(0).toUpperCase()+e.slice(1))),G=U((e=>e?`on${K(e)}`:"")),q=(e,t)=>!Object.is(e,t),J=(e,t)=>{for(let n=0;n<e.length;n++)e[n](t)},Z=(e,t,n)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},Y=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let Q;let X;const ee=[];class te{constructor(e=!1){this.active=!0,this.effects=[],this.cleanups=[],!e&&X&&(this.parent=X,this.index=(X.scopes||(X.scopes=[])).push(this)-1)}run(e){if(this.active)try{return this.on(),e()}finally{this.off()}}on(){this.active&&(ee.push(this),X=this)}off(){this.active&&(ee.pop(),X=ee[ee.length-1])}stop(e){if(this.active){if(this.effects.forEach((e=>e.stop())),this.cleanups.forEach((e=>e())),this.scopes&&this.scopes.forEach((e=>e.stop(!0))),this.parent&&!e){const e=this.parent.scopes.pop();e&&e!==this&&(this.parent.scopes[this.index]=e,e.index=this.index)}this.active=!1}}}function ne(e,t){(t=t||X)&&t.active&&t.effects.push(e)}const oe=e=>{const t=new Set(e);return t.w=0,t.n=0,t},re=e=>(e.w&ce)>0,se=e=>(e.n&ce)>0,ie=new WeakMap;let le=0,cLine truncated
+1 -1
View File
@@ -1263,7 +1263,7 @@ u,
text-decoration: underline;
}
#bar_ui {
#bar-ui {
padding: 0.4em;
display: flex;
flex-wrap: wrap;
-3
View File
@@ -4,9 +4,6 @@
<div id="page">
<h3>{% trans %}404, Not Found{% endtrans %}</h3>
<p class="alert alert-red">
{{ exception }}
</p>
</div>
{% endblock %}
+20 -14
View File
@@ -65,20 +65,26 @@
<div id="header_bar">
<ul id="header_bars_infos">
{% cache 100 "counters_activity" %}
{% for bar in Counter.objects.filter(type="BAR").all() %}
<li>
<a href="{{ url('counter:activity', counter_id=bar.id) }}" style="padding: 0px">
{% if bar.is_inactive(): %}
<i class="fa fa-question" style="color: #f39c12"></i>
{% elif bar.is_open(): %}
<i class="fa fa-check" style="color: #2ecc71"></i>
{% else %}
<i class="fa fa-times" style="color: #eb2f06"></i>
{% endif %}
{{ bar }}
</a>
</li>
{% endfor %}
{% for bar in Counter.objects.annotate_has_barman(user).filter(type="BAR") %}
<li>
{# If the user is a barman, we redirect him directly to the barman page
else we redirect him to the activity page #}
{% if bar.has_annotated_barman %}
<a href="{{ url('counter:details', counter_id=bar.id) }}" style="padding: 0">
{% else %}
<a href="{{ url('counter:activity', counter_id=bar.id) }}" style="padding: 0">
{% endif %}
{% if bar.is_inactive(): %}
<i class="fa fa-question" style="color: #f39c12"></i>
{% elif bar.is_open(): %}
<i class="fa fa-check" style="color: #2ecc71"></i>
{% else %}
<i class="fa fa-times" style="color: #eb2f06"></i>
{% endif %}
{{ bar }}
</a>
</li>
{% endfor %}
</ul>
{% endcache %}
<form action="{{ url('core:search') }}" method="GET" id="header_search">
+33 -30
View File
@@ -290,33 +290,40 @@ class MarkdownTest(TestCase):
class PageHandlingTest(TestCase):
def setUp(self):
try:
Group.objects.create(name="root")
u = User(
username="root",
last_name="",
first_name="Bibou",
email="ae.info@utbm.fr",
date_of_birth="1942-06-12",
is_superuser=True,
is_staff=True,
)
u.set_password("plop")
u.save()
self.client.login(username="root", password="plop")
except Exception as e:
print(e)
self.root_group = Group.objects.create(name="root")
u = User(
username="root",
last_name="",
first_name="Bibou",
email="ae.info@utbm.fr",
date_of_birth="1942-06-12",
is_superuser=True,
is_staff=True,
)
u.set_password("plop")
u.save()
self.client.login(username="root", password="plop")
def test_create_page_ok(self):
"""
Should create a page correctly
"""
self.client.post(
reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": 1}
response = self.client.post(
reverse("core:page_new"),
{"parent": "", "name": "guy", "owner_group": self.root_group.id},
)
self.assertRedirects(
response, reverse("core:page", kwargs={"page_name": "guy"})
)
self.assertTrue(Page.objects.filter(name="guy").exists())
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/guy/hist">' in str(response.content))
self.assertEqual(response.status_code, 200)
html = response.content.decode()
self.assertIn('<a href="/page/guy/hist/">', html)
self.assertIn('<a href="/page/guy/edit/">', html)
self.assertIn('<a href="/page/guy/prop/">', html)
def test_create_child_page_ok(self):
"""
@@ -339,29 +346,25 @@ class PageHandlingTest(TestCase):
"""
Should display a page correctly
"""
parent = Page(name="guy", owner_group=Group.objects.filter(id=1).first())
parent = Page(name="guy", owner_group=self.root_group)
parent.save(force_lock=True)
page = Page(
name="bibou", owner_group=Group.objects.filter(id=1).first(), parent=parent
)
page = Page(name="bibou", owner_group=self.root_group, parent=parent)
page.save(force_lock=True)
response = self.client.get(
reverse("core:page", kwargs={"page_name": "guy/bibou"})
)
self.assertTrue(response.status_code == 200)
self.assertTrue(
'<a href="/page/guy/bibou/edit">\\xc3\\x89diter</a>'
in str(response.content)
)
html = response.content.decode()
self.assertIn('<a href="/page/guy/bibou/edit/">', html)
def test_access_page_not_found(self):
"""
Should not display a page correctly
"""
response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"}))
response = self.client.get("/page/swagg/")
self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/create?page=swagg">' in str(response.content))
html = response.content.decode()
self.assertIn('<a href="/page/create/?page=swagg">', html)
def test_create_page_markdown_safe(self):
"""
+90 -91
View File
@@ -23,40 +23,46 @@
#
#
from django.urls import re_path, path
from django.urls import path, re_path, register_converter
from core.views import *
from core.converters import (
FourDigitYearConverter,
TwoDigitMonthConverter,
BooleanStringConverter,
)
register_converter(FourDigitYearConverter, "yyyy")
register_converter(TwoDigitMonthConverter, "mm")
register_converter(BooleanStringConverter, "bool")
urlpatterns = [
re_path(r"^$", index, name="index"),
re_path(r"^to_markdown$", ToMarkdownView.as_view(), name="to_markdown"),
re_path(r"^notifications$", NotificationList.as_view(), name="notification_list"),
re_path(r"^notification/(?P<notif_id>[0-9]+)$", notification, name="notification"),
path("", index, name="index"),
path("to_markdown/", ToMarkdownView.as_view(), name="to_markdown"),
path("notifications/", NotificationList.as_view(), name="notification_list"),
path("notification/<int:notif_id>/", notification, name="notification"),
# Search
re_path(r"^search/$", search_view, name="search"),
re_path(r"^search_json/$", search_json, name="search_json"),
re_path(r"^search_user/$", search_user_json, name="search_user"),
path("search/", search_view, name="search"),
path("search_json/", search_json, name="search_json"),
path("search_user/", search_user_json, name="search_user"),
# Login and co
re_path(r"^login/$", SithLoginView.as_view(), name="login"),
re_path(r"^logout/$", logout, name="logout"),
re_path(
r"^password_change/$", SithPasswordChangeView.as_view(), name="password_change"
),
re_path(
r"^password_change/(?P<user_id>[0-9]+)$",
path("login/", SithLoginView.as_view(), name="login"),
path("logout/", logout, name="logout"),
path("password_change/", SithPasswordChangeView.as_view(), name="password_change"),
path(
"password_change/<int:user_id>/",
password_root_change,
name="password_root_change",
),
re_path(
r"^password_change/done$",
path(
"password_change/done/",
SithPasswordChangeDoneView.as_view(),
name="password_change_done",
),
re_path(
r"^password_reset/$", SithPasswordResetView.as_view(), name="password_reset"
),
re_path(
r"^password_reset/done$",
path("password_reset/", SithPasswordResetView.as_view(), name="password_reset"),
path(
"password_reset/done/",
SithPasswordResetDoneView.as_view(),
name="password_reset_done",
),
@@ -65,110 +71,103 @@ urlpatterns = [
SithPasswordResetConfirmView.as_view(),
name="password_reset_confirm",
),
re_path(
r"^reset/done/$",
path(
"reset/done/",
SithPasswordResetCompleteView.as_view(),
name="password_reset_complete",
),
re_path(r"^register$", register, name="register"),
path("register/", register, name="register"),
# Group handling
re_path(r"^group/$", GroupListView.as_view(), name="group_list"),
re_path(r"^group/new/$", GroupCreateView.as_view(), name="group_new"),
re_path(
r"^group/(?P<group_id>[0-9]+)/$", GroupEditView.as_view(), name="group_edit"
),
re_path(
r"^group/(?P<group_id>[0-9]+)/delete$",
path("group/", GroupListView.as_view(), name="group_list"),
path("group/new/", GroupCreateView.as_view(), name="group_new"),
path("group/<int:group_id>/", GroupEditView.as_view(), name="group_edit"),
path(
"group/<int:group_id>/delete/",
GroupDeleteView.as_view(),
name="group_delete",
),
re_path(
r"^group/(?P<group_id>[0-9]+)/detail$",
path(
"group/<int:group_id>/detail/",
GroupTemplateView.as_view(),
name="group_detail",
),
# User views
re_path(r"^user/$", UserListView.as_view(), name="user_list"),
re_path(
r"^user/(?P<user_id>[0-9]+)/mini$",
path("user/", UserListView.as_view(), name="user_list"),
path(
"user/<int:user_id>/mini/",
UserMiniView.as_view(),
name="user_profile_mini",
),
re_path(r"^user/(?P<user_id>[0-9]+)/$", UserView.as_view(), name="user_profile"),
re_path(
r"^user/(?P<user_id>[0-9]+)/pictures$",
path("user/<int:user_id>/", UserView.as_view(), name="user_profile"),
path(
"user/<int:user_id>/pictures/",
UserPicturesView.as_view(),
name="user_pictures",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/godfathers$",
path(
"user/<int:user_id>/godfathers/",
UserGodfathersView.as_view(),
name="user_godfathers",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/godfathers/tree$",
path(
"user/<int:user_id>/godfathers/tree/",
UserGodfathersTreeView.as_view(),
name="user_godfathers_tree",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/godfathers/tree/pict$",
path(
"user/<int:user_id>/godfathers/tree/pict/",
UserGodfathersTreePictureView.as_view(),
name="user_godfathers_tree_pict",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/godfathers/(?P<godfather_id>[0-9]+)/(?P<is_father>(True)|(False))/delete$",
DeleteUserGodfathers,
path(
"user/<int:user_id>/godfathers/<int:godfather_id>/<bool:is_father>/delete/",
delete_user_godfather,
name="user_godfathers_delete",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/edit$",
path(
"user/<int:user_id>/edit/",
UserUpdateProfileView.as_view(),
name="user_edit",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/profile_upload$",
path(
"user/<int:user_id>/profile_upload/",
UserUploadProfilePictView.as_view(),
name="user_profile_upload",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/clubs$", UserClubView.as_view(), name="user_clubs"
),
re_path(
r"^user/(?P<user_id>[0-9]+)/prefs$",
path("user/<int:user_id>/clubs/", UserClubView.as_view(), name="user_clubs"),
path(
"user/<int:user_id>/prefs/",
UserPreferencesView.as_view(),
name="user_prefs",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/groups$",
path(
"user/<int:user_id>/groups/",
UserUpdateGroupView.as_view(),
name="user_groups",
),
re_path(r"^user/tools/$", UserToolsView.as_view(), name="user_tools"),
re_path(
r"^user/(?P<user_id>[0-9]+)/account$",
path("user/tools/", UserToolsView.as_view(), name="user_tools"),
path(
"user/<int:user_id>/account/",
UserAccountView.as_view(),
name="user_account",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/account/(?P<year>[0-9]+)/(?P<month>[0-9]+)$",
path(
"user/<int:user_id>/account/<yyyy:year>/<mm:month>/",
UserAccountDetailView.as_view(),
name="user_account_detail",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/stats$", UserStatsView.as_view(), name="user_stats"
),
re_path(
r"^user/(?P<user_id>[0-9]+)/gift/create$",
path("user/<int:user_id>/stats/", UserStatsView.as_view(), name="user_stats"),
path(
"user/<int:user_id>/gift/create/",
GiftCreateView.as_view(),
name="user_gift_create",
),
re_path(
r"^user/(?P<user_id>[0-9]+)/gift/delete/(?P<gift_id>[0-9]+)/$",
path(
"user/<int:user_id>/gift/delete/<int:gift_id>/",
GiftDeleteView.as_view(),
name="user_gift_delete",
),
# File views
# re_path(r'^file/add/(?P<popup>popup)?$', FileCreateView.as_view(), name='file_new'),
re_path(r"^file/(?P<popup>popup)?$", FileListView.as_view(), name="file_list"),
re_path(
r"^file/(?P<file_id>[0-9]+)/(?P<popup>popup)?$",
@@ -190,43 +189,43 @@ urlpatterns = [
FileDeleteView.as_view(),
name="file_delete",
),
re_path(r"^file/moderation$", FileModerationView.as_view(), name="file_moderation"),
re_path(
r"^file/(?P<file_id>[0-9]+)/moderate$",
path("file/moderation/", FileModerationView.as_view(), name="file_moderation"),
path(
"file/<int:file_id>/moderate/",
FileModerateView.as_view(),
name="file_moderate",
),
re_path(r"^file/(?P<file_id>[0-9]+)/download$", send_file, name="download"),
path("file/<int:file_id>/download/", send_file, name="download"),
# Page views
re_path(r"^page/$", PageListView.as_view(), name="page_list"),
re_path(r"^page/create$", PageCreateView.as_view(), name="page_new"),
re_path(
r"^page/(?P<page_id>[0-9]*)/delete$",
path("page/", PageListView.as_view(), name="page_list"),
path("page/create/", PageCreateView.as_view(), name="page_new"),
path(
"page/<int:page_id>/delete/",
PageDeleteView.as_view(),
name="page_delete",
),
re_path(
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/edit$",
path(
"page/<path:page_name>/edit/",
PageEditView.as_view(),
name="page_edit",
),
re_path(
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/prop$",
path(
"page/<path:page_name>/prop/",
PagePropView.as_view(),
name="page_prop",
),
re_path(
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/hist$",
path(
"page/<path:page_name>/hist/",
PageHistView.as_view(),
name="page_hist",
),
re_path(
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/rev/(?P<rev>[0-9]+)/",
path(
"page/<path:page_name>/rev/<int:rev>/",
PageRevView.as_view(),
name="page_rev",
),
re_path(
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/$",
path(
"page/<path:page_name>/",
PageView.as_view(),
name="page",
),
+10 -12
View File
@@ -207,9 +207,7 @@ class UserTabsMixin(TabedViewMixin):
"name": _("Pictures"),
},
]
if (
False and self.request.user.was_subscribed
): # TODO: display galaxy once it's ready
if settings.SITH_ENABLE_GALAXY and self.request.user.was_subscribed:
tab_list.append(
{
"url": reverse("galaxy:user", kwargs={"user_id": user.id}),
@@ -330,16 +328,16 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
return kwargs
def DeleteUserGodfathers(request, user_id, godfather_id, is_father):
user = User.objects.get(id=user_id)
if (user == request.user) or request.user.is_root or request.user.is_board_member:
ud = get_object_or_404(User, id=godfather_id)
if is_father == "True":
user.godfathers.remove(ud)
else:
user.godchildren.remove(ud)
def delete_user_godfather(request, user_id, godfather_id, is_father):
user_is_admin = request.user.is_root or request.user.is_board_member
if user_id != request.user.id and not user_is_admin:
raise PermissionDenied()
user = get_object_or_404(User, id=user_id)
to_remove = get_object_or_404(User, id=godfather_id)
if is_father:
user.godfathers.remove(to_remove)
else:
raise PermissionDenied
user.godchildren.remove(to_remove)
return redirect("core:user_godfathers", user_id=user_id)