mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-10-31 17:13:08 +00:00 
			
		
		
		
	Introduce htmx in sith files
* Convert FileModerationView into ListView and add pagination with htmx * Don't allow sas moderation in file moderation view * Split up base.jinja and introduce base_fragment.jinja * Improve FileModerationView performances and make it root only * Add permissions tests for file modération
This commit is contained in:
		
							
								
								
									
										1
									
								
								core/static/webpack/htmx-index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								core/static/webpack/htmx-index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | window.htmx = require("htmx.org"); | ||||||
| @@ -5,7 +5,6 @@ | |||||||
|       <title>{% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %} - Association des Étudiants UTBM</title> |       <title>{% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %} - Association des Étudiants UTBM</title> | ||||||
|       <meta name="viewport" content="width=device-width, initial-scale=1.0"> |       <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||||
|       <link rel="shortcut icon" href="{{ static('core/img/favicon.ico') }}"> |       <link rel="shortcut icon" href="{{ static('core/img/favicon.ico') }}"> | ||||||
|       <link rel="stylesheet" href="{{ static('user/user_stats.scss') }}"> |  | ||||||
|       <link rel="stylesheet" href="{{ static('core/base.css') }}"> |       <link rel="stylesheet" href="{{ static('core/base.css') }}"> | ||||||
|       <link rel="stylesheet" href="{{ static('core/style.scss') }}"> |       <link rel="stylesheet" href="{{ static('core/style.scss') }}"> | ||||||
|       <link rel="stylesheet" href="{{ static('core/markdown.scss') }}"> |       <link rel="stylesheet" href="{{ static('core/markdown.scss') }}"> | ||||||
| @@ -14,7 +13,7 @@ | |||||||
|       <link rel="stylesheet" href="{{ static('core/pagination.scss') }}"> |       <link rel="stylesheet" href="{{ static('core/pagination.scss') }}"> | ||||||
|  |  | ||||||
|       {% block jquery_css %} |       {% block jquery_css %} | ||||||
|                 {# Thile file is quite heavy (around 250kb), so declaring it in a block allows easy removal #} |         {# Thile file is quite heavy (around 250kb), so declaring it in a block allows easy removal #} | ||||||
|         <link rel="stylesheet" href="{{ static('webpack/jquery-index.css') }}"> |         <link rel="stylesheet" href="{{ static('webpack/jquery-index.css') }}"> | ||||||
|       {% endblock %} |       {% endblock %} | ||||||
|       <link rel="preload" as="style" href="{{ static('webpack/fontawesome-index.css') }}" onload="this.onload=null;this.rel='stylesheet'"> |       <link rel="preload" as="style" href="{{ static('webpack/fontawesome-index.css') }}" onload="this.onload=null;this.rel='stylesheet'"> | ||||||
| @@ -23,6 +22,7 @@ | |||||||
|       <script src="{{ url('javascript-catalog') }}"></script> |       <script src="{{ url('javascript-catalog') }}"></script> | ||||||
|       <script src={{ static("webpack/core/components/include-index.ts") }}></script> |       <script src={{ static("webpack/core/components/include-index.ts") }}></script> | ||||||
|       <script src="{{ static('webpack/alpine-index.js') }}" defer></script> |       <script src="{{ static('webpack/alpine-index.js') }}" defer></script> | ||||||
|  |       <script src="{{ static('webpack/htmx-index.js') }}" defer></script> | ||||||
|             <!-- Jquery declared here to be accessible in every django widgets --> |             <!-- Jquery declared here to be accessible in every django widgets --> | ||||||
|       <script src="{{ static('webpack/jquery-index.js') }}"></script> |       <script src="{{ static('webpack/jquery-index.js') }}"></script> | ||||||
|             <!-- Put here to always have access to those functions on django widgets --> |             <!-- Put here to always have access to those functions on django widgets --> | ||||||
| @@ -37,222 +37,37 @@ | |||||||
|  |  | ||||||
|   <body> |   <body> | ||||||
|  |  | ||||||
|         <!-- The token is always passed here to be accessible from the dom --> |     <!-- The token is always passed here to be accessible from the dom --> | ||||||
|         <!-- See this workaround https://docs.djangoproject.com/en/2.0/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-is-true --> |     <!-- See this workaround https://docs.djangoproject.com/en/2.0/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-is-true --> | ||||||
|     {% csrf_token %} |     {% csrf_token %} | ||||||
|         <!-- BEGIN HEADER --> |  | ||||||
|     {% block header %} |     {% block header %} | ||||||
|       {% if not popup %} |       {% if not popup %} | ||||||
|         <header class="header"> |         {% include "core/base/header.jinja" %} | ||||||
|           <div class="header-logo"> |  | ||||||
|             <a class="header-logo-picture" href="{{ url('core:index') }}" style="background-image: url('{{ static('core/img/logo_no_text.png') }}')"> |  | ||||||
|                 |  | ||||||
|             </a> |  | ||||||
|             <a class="header-logo-text" href="{{ url('core:index') }}"> |  | ||||||
|               <span>Association des Étudiants</span> |  | ||||||
|               <span>de l'Université de Technologie de Belfort-Montbéliard</span> |  | ||||||
|             </a> |  | ||||||
|           </div> |  | ||||||
|           {% if not user.is_authenticated %} |  | ||||||
|             <div class="header-disconnected"> |  | ||||||
|               <a class="button" href="{{ url('core:login') }}">{% trans %}Login{% endtrans %}</a> |  | ||||||
|               <a class="button" href="{{ url('core:register') }}">{% trans %}Register{% endtrans %}</a> |  | ||||||
|             </div> |  | ||||||
|           {% else %} |  | ||||||
|             <div class="header-connected"> |  | ||||||
|               <div class="left"> |  | ||||||
|                 <form class="search" action="{{ url('core:search') }}" method="GET" id="header_search"> |  | ||||||
|                   <input class="header-input" type="text" placeholder="{% trans %}Search{% endtrans %}" name="query" id="search" /> |  | ||||||
|                   <input type="submit" value="{% trans %}Search{% endtrans %}" style="display: none;" /> |  | ||||||
|                 </form> |  | ||||||
|                 <ul class="bars"> |  | ||||||
|                   {% cache 100 "counters_activity" %} |  | ||||||
|                       {# The sith has no periodic tasks manager |  | ||||||
|                          and using cron jobs would be way too overkill here. |  | ||||||
|                          Thus the barmen timeout is handled in the only place that |  | ||||||
|                          is loaded on every page : the header bar. |  | ||||||
|                          However, let's be clear : this has nothing to do here. |  | ||||||
|                          It's' merely a contrived workaround that should |  | ||||||
|                          replaced by a proper task manager as soon as possible. #} |  | ||||||
|                     {% set _ = Counter.objects.filter(type="BAR").handle_timeout() %} |  | ||||||
|                   {% endcache %} |  | ||||||
|                   {% for bar in Counter.objects.annotate_has_barman(user).annotate_is_open().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) }}"> |  | ||||||
|                       {% else %} |  | ||||||
|                         <a href="{{ url('counter:activity', counter_id=bar.id) }}"> |  | ||||||
|                       {% endif %} |  | ||||||
|                       {% if bar.is_open %} |  | ||||||
|                         <i class="fa fa-check" style="color: #2ecc71"></i> |  | ||||||
|                       {% else %} |  | ||||||
|                         <i class="fa fa-times" style="color: #eb2f06"></i> |  | ||||||
|                       {% endif %} |  | ||||||
|                       <span>{{ bar }}</span> |  | ||||||
|                     </a> |  | ||||||
|                     </li> |  | ||||||
|                   {% endfor %} |  | ||||||
|                 </ul> |  | ||||||
|               </div> |  | ||||||
|               <div class="right"> |  | ||||||
|                 <div class="user"> |  | ||||||
|                   <div class="options"> |  | ||||||
|                     <div class="username"> |  | ||||||
|                       <a href="{{ url('core:user_profile', user_id=user.id) }}">{{ user.get_display_name() }}</a> |  | ||||||
|                     </div> |  | ||||||
|                     <div class="links"> |  | ||||||
|                       <a href="{{ url('core:user_tools') }}">{% trans %}Tools{% endtrans %}</a> |  | ||||||
|                       <a href="{{ url('core:logout') }}">{% trans %}Logout{% endtrans %}</a> |  | ||||||
|                     </div> |  | ||||||
|                   </div> |  | ||||||
|                   <a |  | ||||||
|                     href="{{ url('core:user_profile', user_id=user.id) }}" |  | ||||||
|                     {% if user.profile_pict %} |  | ||||||
|                       style="background-image: url('{{ user.profile_pict.get_download_url() }}')" |  | ||||||
|                     {% else %} |  | ||||||
|                       style="background-image: url('{{ static('core/img/unknown.jpg') }}')" |  | ||||||
|                     {% endif %} |  | ||||||
|                   ></a> |  | ||||||
|                 </div> |  | ||||||
|                 <div class="notification"> |  | ||||||
|                   <a href="#" onclick="displayNotif()"> |  | ||||||
|                     <i class="fa-regular fa-bell"></i> |  | ||||||
|                     {% set notification_count = user.notifications.filter(viewed=False).count() %} |  | ||||||
|  |  | ||||||
|                     {% if notification_count > 0 %} |  | ||||||
|                       <span> |  | ||||||
|                         {% if notification_count < 100 %} |  | ||||||
|                           {{ notification_count }} |  | ||||||
|                         {% else %} |  | ||||||
|                             |  | ||||||
|                         {% endif %} |  | ||||||
|                       </span> |  | ||||||
|                     {% endif %} |  | ||||||
|                   </a> |  | ||||||
|                   <div id="header_notif"> |  | ||||||
|                     <ul> |  | ||||||
|                       {% if user.notifications.filter(viewed=False).count() > 0 %} |  | ||||||
|                         {% for n in user.notifications.filter(viewed=False).order_by('-date') %} |  | ||||||
|                           <li> |  | ||||||
|                             <a href="{{ url("core:notification", notif_id=n.id) }}"> |  | ||||||
|                               <div class="datetime"> |  | ||||||
|                                 <span class="header_notif_date"> |  | ||||||
|                                   {{ n.date|localtime|date(DATE_FORMAT) }} |  | ||||||
|                                 </span> |  | ||||||
|                                 <span class="header_notif_time"> |  | ||||||
|                                   {{ n.date|localtime|time(DATETIME_FORMAT) }} |  | ||||||
|                                 </span> |  | ||||||
|                               </div> |  | ||||||
|                               <div class="reason"> |  | ||||||
|                                 {{ n }} |  | ||||||
|                               </div> |  | ||||||
|                             </a> |  | ||||||
|                           </li> |  | ||||||
|                         {% endfor %} |  | ||||||
|                       {% else %} |  | ||||||
|                         <li class="empty-notification">{% trans %}You do not have any unread notification{% endtrans %}</li> |  | ||||||
|                       {% endif %} |  | ||||||
|                     </ul> |  | ||||||
|                     <div class="options"> |  | ||||||
|                       <a href="{{ url('core:notification_list') }}"> |  | ||||||
|                         {% trans %}View more{% endtrans %} |  | ||||||
|                       </a> |  | ||||||
|                       <a href="{{ url('core:notification_list') }}?see_all"> |  | ||||||
|                         {% trans %}Mark all as read{% endtrans %} |  | ||||||
|                       </a> |  | ||||||
|                     </div> |  | ||||||
|                   </div> |  | ||||||
|                 </div> |  | ||||||
|               </div> |  | ||||||
|             </div> |  | ||||||
|           {% endif %} |  | ||||||
|           <div class="header-lang"> |  | ||||||
|             {% for language in LANGUAGES %} |  | ||||||
|               <form action="{{ url('set_language') }}" method="post"> |  | ||||||
|                 {% csrf_token %} |  | ||||||
|                 <input name="next" value="{{ request.path }}" type="hidden" /> |  | ||||||
|                 <input name="language" value="{{ language[0] }}" type="hidden" /> |  | ||||||
|                 <input type="submit" value="{% if language[0] == 'en' %}🇬🇧{% else %}🇫🇷{% endif %}" /> |  | ||||||
|               </form> |  | ||||||
|             {% endfor %} |  | ||||||
|           </div> |  | ||||||
|         </header> |  | ||||||
|  |  | ||||||
|         {% block info_boxes %} |  | ||||||
|           <div id="info_boxes"> |  | ||||||
|             {% set sith = get_sith() %} |  | ||||||
|             {% if sith.alert_msg %} |  | ||||||
|               <div id="alert_box"> |  | ||||||
|                 {{ sith.alert_msg|markdown }} |  | ||||||
|               </div> |  | ||||||
|             {% endif %} |  | ||||||
|             {% if sith.info_msg %} |  | ||||||
|               <div id="info_box"> |  | ||||||
|                 {{ sith.info_msg|markdown }} |  | ||||||
|               </div> |  | ||||||
|             {% endif %} |  | ||||||
|           </div> |  | ||||||
|         {% endblock %} |  | ||||||
|  |  | ||||||
|       {% else %} |       {% else %} | ||||||
|         <div id="popupheader">{{ user.get_display_name() }}</div> |         <div id="popupheader">{{ user.get_display_name() }}</div> | ||||||
|       {% endif %} |       {% endif %} | ||||||
|  |  | ||||||
|  |       {% block info_boxes %} | ||||||
|  |         <div id="info_boxes"> | ||||||
|  |           {% set sith = get_sith() %} | ||||||
|  |           {% if sith.alert_msg %} | ||||||
|  |             <div id="alert_box"> | ||||||
|  |               {{ sith.alert_msg|markdown }} | ||||||
|  |             </div> | ||||||
|  |           {% endif %} | ||||||
|  |           {% if sith.info_msg %} | ||||||
|  |             <div id="info_box"> | ||||||
|  |               {{ sith.info_msg|markdown }} | ||||||
|  |             </div> | ||||||
|  |           {% endif %} | ||||||
|  |         </div> | ||||||
|  |       {% endblock %} | ||||||
|     {% endblock %} |     {% endblock %} | ||||||
|         <!-- END HEADER --> |  | ||||||
|  |  | ||||||
|     {% block nav %} |     {% block nav %} | ||||||
|       {% if not popup %} |       {% if not popup %} | ||||||
|         <nav class="navbar"> |         {% include "core/base/menu.jinja" %} | ||||||
|           <button class="expand-button" onclick="showMenu()"><i class="fa fa-bars"></i></button> |  | ||||||
|           <div id="navbar-content" class="content" style="display: none;"> |  | ||||||
|             <a class="link" href="{{ url('core:index') }}">{% trans %}Main{% endtrans %}</a> |  | ||||||
|             <div class="menu"> |  | ||||||
|               <span class="head">{% trans %}Associations & Clubs{% endtrans %}</span> |  | ||||||
|               <ul class="content"> |  | ||||||
|                 <li><a href="{{ url('core:page', page_name='ae') }}">{% trans %}AE{% endtrans %}</a></li> |  | ||||||
|                 <li><a href="{{ url('core:page', page_name='clubs') }}">{% trans %}AE's clubs{% endtrans %}</a></li> |  | ||||||
|                 <li><a href="{{ url('core:page', page_name='utbm-associations') }}">{% trans %}Others UTBM's Associations{% endtrans %}</a></li> |  | ||||||
|               </ul> |  | ||||||
|             </div> |  | ||||||
|             <div class="menu"> |  | ||||||
|               <span class="head">{% trans %}Events{% endtrans %}</span> |  | ||||||
|               <ul class="content"> |  | ||||||
|                 <li><a href="{{ url('election:list') }}">{% trans %}Elections{% endtrans %}</a></li> |  | ||||||
|                 <li><a href="{{ url('core:page', page_name='ga') }}">{% trans %}Big event{% endtrans %}</a></li> |  | ||||||
|               </ul> |  | ||||||
|             </div> |  | ||||||
|             <a class="link" href="{{ url('forum:main') }}">{% trans %}Forum{% endtrans %}</a> |  | ||||||
|             <a class="link" href="{{ url('sas:main') }}">{% trans %}Gallery{% endtrans %}</a> |  | ||||||
|             <a class="link" href="{{ url('eboutic:main') }}">{% trans %}Eboutic{% endtrans %}</a> |  | ||||||
|             <div class="menu"> |  | ||||||
|               <span class="head">{% trans %}Services{% endtrans %}</span> |  | ||||||
|               <ul class="content"> |  | ||||||
|                 <li><a href="{{ url('matmat:search_clear') }}">{% trans %}Matmatronch{% endtrans %}</a></li> |  | ||||||
|                 <li><a href="/launderette">{% trans %}Launderette{% endtrans %}</a></li> |  | ||||||
|                 <li><a href="{{ url('core:file_list') }}">{% trans %}Files{% endtrans %}</a></li> |  | ||||||
|                 <li><a href="{{ url('pedagogy:guide') }}">{% trans %}Pedagogy{% endtrans %}</a></li> |  | ||||||
|               </ul> |  | ||||||
|             </div> |  | ||||||
|             <div class="menu"> |  | ||||||
|               <span class="head">{% trans %}My Benefits{% endtrans %}</span> |  | ||||||
|               <ul class="content"> |  | ||||||
|                 <li><a href="{{ url('core:page', page_name='partenaires')}}">{% trans %}Sponsors{% endtrans %}</a></li> |  | ||||||
|                 <li><a href="{{ url('core:page', page_name='avantages') }}">{% trans %}Subscriber benefits{% endtrans %}</a></li> |  | ||||||
|               </ul> |  | ||||||
|             </div> |  | ||||||
|             <div class="menu"> |  | ||||||
|               <span class="head">{% trans %}Help{% endtrans %}</span> |  | ||||||
|               <ul class="content"> |  | ||||||
|                 <li><a href="{{ url('core:page', page_name='FAQ') }}">{% trans %}FAQ{% endtrans %}</a></li> |  | ||||||
|                 <li><a href="{{ url('core:page', 'contacts') }}">{% trans %}Contacts{% endtrans %}</a></li> |  | ||||||
|                 <li><a href="{{ url('core:page', page_name='Index') }}">{% trans %}Wiki{% endtrans %}</a></li> |  | ||||||
|               </ul> |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|         </nav> |  | ||||||
|       {% endif %} |       {% endif %} | ||||||
|     {% endblock %} |     {% endblock %} | ||||||
|  |  | ||||||
| @@ -265,19 +80,16 @@ | |||||||
|       </ul> |       </ul> | ||||||
|  |  | ||||||
|       <div id="content"> |       <div id="content"> | ||||||
|         {% if list_of_tabs %} |         {% block tabs %} | ||||||
|           <div class="tool_bar"> |           {% include "core/base/tabs.jinja" %} | ||||||
|             <div class="tools"> |         {% endblock %} | ||||||
|               {% for t in list_of_tabs -%} |  | ||||||
|                 <a href="{{ t.url }}" {%- if current_tab==t.slug %} class="selected_tab" {%- endif -%}>{{ t.name }}</a> |         {% block errors%} | ||||||
|               {%- endfor %} |           {% if error %} | ||||||
|             </div> |             {{ error }} | ||||||
|           </div> |           {% endif %} | ||||||
|         {% endif %} |         {% endblock %} | ||||||
|  |  | ||||||
|         {% if error %} |  | ||||||
|           {{ error }} |  | ||||||
|         {% endif %} |  | ||||||
|         {% block content %} |         {% block content %} | ||||||
|         {% endblock %} |         {% endblock %} | ||||||
|       </div> |       </div> | ||||||
|   | |||||||
							
								
								
									
										136
									
								
								core/templates/core/base/header.jinja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								core/templates/core/base/header.jinja
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | <header class="header"> | ||||||
|  |   <div class="header-logo"> | ||||||
|  |     <a class="header-logo-picture" href="{{ url('core:index') }}" style="background-image: url('{{ static('core/img/logo_no_text.png') }}')"> | ||||||
|  |         | ||||||
|  |     </a> | ||||||
|  |     <a class="header-logo-text" href="{{ url('core:index') }}"> | ||||||
|  |       <span>Association des Étudiants</span> | ||||||
|  |       <span>de l'Université de Technologie de Belfort-Montbéliard</span> | ||||||
|  |     </a> | ||||||
|  |   </div> | ||||||
|  |   {% if not user.is_authenticated %} | ||||||
|  |     <div class="header-disconnected"> | ||||||
|  |       <a class="button" href="{{ url('core:login') }}">{% trans %}Login{% endtrans %}</a> | ||||||
|  |       <a class="button" href="{{ url('core:register') }}">{% trans %}Register{% endtrans %}</a> | ||||||
|  |     </div> | ||||||
|  |   {% else %} | ||||||
|  |     <div class="header-connected"> | ||||||
|  |       <div class="left"> | ||||||
|  |         <form class="search" action="{{ url('core:search') }}" method="GET" id="header_search"> | ||||||
|  |           <input class="header-input" type="text" placeholder="{% trans %}Search{% endtrans %}" name="query" id="search" /> | ||||||
|  |           <input type="submit" value="{% trans %}Search{% endtrans %}" style="display: none;" /> | ||||||
|  |         </form> | ||||||
|  |         <ul class="bars"> | ||||||
|  |           {% cache 100 "counters_activity" %} | ||||||
|  |                       {# The sith has no periodic tasks manager | ||||||
|  |                          and using cron jobs would be way too overkill here. | ||||||
|  |                          Thus the barmen timeout is handled in the only place that | ||||||
|  |                          is loaded on every page : the header bar. | ||||||
|  |                          However, let's be clear : this has nothing to do here. | ||||||
|  |                          It's' merely a contrived workaround that should | ||||||
|  |                          replaced by a proper task manager as soon as possible. #} | ||||||
|  |             {% set _ = Counter.objects.filter(type="BAR").handle_timeout() %} | ||||||
|  |           {% endcache %} | ||||||
|  |           {% for bar in Counter.objects.annotate_has_barman(user).annotate_is_open().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) }}"> | ||||||
|  |               {% else %} | ||||||
|  |                 <a href="{{ url('counter:activity', counter_id=bar.id) }}"> | ||||||
|  |               {% endif %} | ||||||
|  |               {% if bar.is_open %} | ||||||
|  |                 <i class="fa fa-check" style="color: #2ecc71"></i> | ||||||
|  |               {% else %} | ||||||
|  |                 <i class="fa fa-times" style="color: #eb2f06"></i> | ||||||
|  |               {% endif %} | ||||||
|  |               <span>{{ bar }}</span> | ||||||
|  |             </a> | ||||||
|  |             </li> | ||||||
|  |           {% endfor %} | ||||||
|  |         </ul> | ||||||
|  |       </div> | ||||||
|  |       <div class="right"> | ||||||
|  |         <div class="user"> | ||||||
|  |           <div class="options"> | ||||||
|  |             <div class="username"> | ||||||
|  |               <a href="{{ url('core:user_profile', user_id=user.id) }}">{{ user.get_display_name() }}</a> | ||||||
|  |             </div> | ||||||
|  |             <div class="links"> | ||||||
|  |               <a href="{{ url('core:user_tools') }}">{% trans %}Tools{% endtrans %}</a> | ||||||
|  |               <a href="{{ url('core:logout') }}">{% trans %}Logout{% endtrans %}</a> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |           <a | ||||||
|  |             href="{{ url('core:user_profile', user_id=user.id) }}" | ||||||
|  |             {% if user.profile_pict %} | ||||||
|  |               style="background-image: url('{{ user.profile_pict.get_download_url() }}')" | ||||||
|  |             {% else %} | ||||||
|  |               style="background-image: url('{{ static('core/img/unknown.jpg') }}')" | ||||||
|  |             {% endif %} | ||||||
|  |           ></a> | ||||||
|  |         </div> | ||||||
|  |         <div class="notification"> | ||||||
|  |           <a href="#" onclick="displayNotif()"> | ||||||
|  |             <i class="fa-regular fa-bell"></i> | ||||||
|  |             {% set notification_count = user.notifications.filter(viewed=False).count() %} | ||||||
|  |  | ||||||
|  |             {% if notification_count > 0 %} | ||||||
|  |               <span> | ||||||
|  |                 {% if notification_count < 100 %} | ||||||
|  |                   {{ notification_count }} | ||||||
|  |                 {% else %} | ||||||
|  |                     | ||||||
|  |                 {% endif %} | ||||||
|  |               </span> | ||||||
|  |             {% endif %} | ||||||
|  |           </a> | ||||||
|  |           <div id="header_notif"> | ||||||
|  |             <ul> | ||||||
|  |               {% if user.notifications.filter(viewed=False).count() > 0 %} | ||||||
|  |                 {% for n in user.notifications.filter(viewed=False).order_by('-date') %} | ||||||
|  |                   <li> | ||||||
|  |                     <a href="{{ url("core:notification", notif_id=n.id) }}"> | ||||||
|  |                       <div class="datetime"> | ||||||
|  |                         <span class="header_notif_date"> | ||||||
|  |                           {{ n.date|localtime|date(DATE_FORMAT) }} | ||||||
|  |                         </span> | ||||||
|  |                         <span class="header_notif_time"> | ||||||
|  |                           {{ n.date|localtime|time(DATETIME_FORMAT) }} | ||||||
|  |                         </span> | ||||||
|  |                       </div> | ||||||
|  |                       <div class="reason"> | ||||||
|  |                         {{ n }} | ||||||
|  |                       </div> | ||||||
|  |                     </a> | ||||||
|  |                   </li> | ||||||
|  |                 {% endfor %} | ||||||
|  |               {% else %} | ||||||
|  |                 <li class="empty-notification">{% trans %}You do not have any unread notification{% endtrans %}</li> | ||||||
|  |               {% endif %} | ||||||
|  |             </ul> | ||||||
|  |             <div class="options"> | ||||||
|  |               <a href="{{ url('core:notification_list') }}"> | ||||||
|  |                 {% trans %}View more{% endtrans %} | ||||||
|  |               </a> | ||||||
|  |               <a href="{{ url('core:notification_list') }}?see_all"> | ||||||
|  |                 {% trans %}Mark all as read{% endtrans %} | ||||||
|  |               </a> | ||||||
|  |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   {% endif %} | ||||||
|  |   <div class="header-lang"> | ||||||
|  |     {% for language in LANGUAGES %} | ||||||
|  |       <form action="{{ url('set_language') }}" method="post"> | ||||||
|  |         {% csrf_token %} | ||||||
|  |         <input name="next" value="{{ request.path }}" type="hidden" /> | ||||||
|  |         <input name="language" value="{{ language[0] }}" type="hidden" /> | ||||||
|  |         <input type="submit" value="{% if language[0] == 'en' %}🇬🇧{% else %}🇫🇷{% endif %}" /> | ||||||
|  |       </form> | ||||||
|  |     {% endfor %} | ||||||
|  |   </div> | ||||||
|  | </header> | ||||||
							
								
								
									
										48
									
								
								core/templates/core/base/menu.jinja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								core/templates/core/base/menu.jinja
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | <nav class="navbar"> | ||||||
|  |   <button class="expand-button" onclick="showMenu()"><i class="fa fa-bars"></i></button> | ||||||
|  |   <div id="navbar-content" class="content" style="display: none;"> | ||||||
|  |     <a class="link" href="{{ url('core:index') }}">{% trans %}Main{% endtrans %}</a> | ||||||
|  |     <div class="menu"> | ||||||
|  |       <span class="head">{% trans %}Associations & Clubs{% endtrans %}</span> | ||||||
|  |       <ul class="content"> | ||||||
|  |         <li><a href="{{ url('core:page', page_name='ae') }}">{% trans %}AE{% endtrans %}</a></li> | ||||||
|  |         <li><a href="{{ url('core:page', page_name='clubs') }}">{% trans %}AE's clubs{% endtrans %}</a></li> | ||||||
|  |         <li><a href="{{ url('core:page', page_name='utbm-associations') }}">{% trans %}Others UTBM's Associations{% endtrans %}</a></li> | ||||||
|  |       </ul> | ||||||
|  |     </div> | ||||||
|  |     <div class="menu"> | ||||||
|  |       <span class="head">{% trans %}Events{% endtrans %}</span> | ||||||
|  |       <ul class="content"> | ||||||
|  |         <li><a href="{{ url('election:list') }}">{% trans %}Elections{% endtrans %}</a></li> | ||||||
|  |         <li><a href="{{ url('core:page', page_name='ga') }}">{% trans %}Big event{% endtrans %}</a></li> | ||||||
|  |       </ul> | ||||||
|  |     </div> | ||||||
|  |     <a class="link" href="{{ url('forum:main') }}">{% trans %}Forum{% endtrans %}</a> | ||||||
|  |     <a class="link" href="{{ url('sas:main') }}">{% trans %}Gallery{% endtrans %}</a> | ||||||
|  |     <a class="link" href="{{ url('eboutic:main') }}">{% trans %}Eboutic{% endtrans %}</a> | ||||||
|  |     <div class="menu"> | ||||||
|  |       <span class="head">{% trans %}Services{% endtrans %}</span> | ||||||
|  |       <ul class="content"> | ||||||
|  |         <li><a href="{{ url('matmat:search_clear') }}">{% trans %}Matmatronch{% endtrans %}</a></li> | ||||||
|  |         <li><a href="/launderette">{% trans %}Launderette{% endtrans %}</a></li> | ||||||
|  |         <li><a href="{{ url('core:file_list') }}">{% trans %}Files{% endtrans %}</a></li> | ||||||
|  |         <li><a href="{{ url('pedagogy:guide') }}">{% trans %}Pedagogy{% endtrans %}</a></li> | ||||||
|  |       </ul> | ||||||
|  |     </div> | ||||||
|  |     <div class="menu"> | ||||||
|  |       <span class="head">{% trans %}My Benefits{% endtrans %}</span> | ||||||
|  |       <ul class="content"> | ||||||
|  |         <li><a href="{{ url('core:page', page_name='partenaires')}}">{% trans %}Sponsors{% endtrans %}</a></li> | ||||||
|  |         <li><a href="{{ url('core:page', page_name='avantages') }}">{% trans %}Subscriber benefits{% endtrans %}</a></li> | ||||||
|  |       </ul> | ||||||
|  |     </div> | ||||||
|  |     <div class="menu"> | ||||||
|  |       <span class="head">{% trans %}Help{% endtrans %}</span> | ||||||
|  |       <ul class="content"> | ||||||
|  |         <li><a href="{{ url('core:page', page_name='FAQ') }}">{% trans %}FAQ{% endtrans %}</a></li> | ||||||
|  |         <li><a href="{{ url('core:page', 'contacts') }}">{% trans %}Contacts{% endtrans %}</a></li> | ||||||
|  |         <li><a href="{{ url('core:page', page_name='Index') }}">{% trans %}Wiki{% endtrans %}</a></li> | ||||||
|  |       </ul> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </nav> | ||||||
							
								
								
									
										9
									
								
								core/templates/core/base/tabs.jinja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								core/templates/core/base/tabs.jinja
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | {% if list_of_tabs %} | ||||||
|  |   <div class="tool_bar"> | ||||||
|  |     <div class="tools"> | ||||||
|  |       {% for t in list_of_tabs -%} | ||||||
|  |         <a href="{{ t.url }}" {%- if current_tab==t.slug %} class="selected_tab" {%- endif -%}>{{ t.name }}</a> | ||||||
|  |       {%- endfor %} | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | {% endif %} | ||||||
							
								
								
									
										20
									
								
								core/templates/core/base_fragment.jinja
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								core/templates/core/base_fragment.jinja
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | {% block additional_css %}{% endblock %} | ||||||
|  | {% block additional_js %}{% endblock %} | ||||||
|  |  | ||||||
|  | <div id="fragment-content"> | ||||||
|  |   {% block tabs %} | ||||||
|  |     {% include "core/base/tabs.jinja" %} | ||||||
|  |   {% endblock %} | ||||||
|  |  | ||||||
|  |   {% block errors%} | ||||||
|  |     {% if error %} | ||||||
|  |       {{ error }} | ||||||
|  |     {% endif %} | ||||||
|  |   {% endblock %} | ||||||
|  |  | ||||||
|  |   {% block content %} | ||||||
|  |   {% endblock %} | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | {% block script %} | ||||||
|  | {% endblock %} | ||||||
| @@ -1,4 +1,8 @@ | |||||||
| {% extends "core/base.jinja" %} | {% if is_fragment %} | ||||||
|  |   {% extends "core/base_fragment.jinja" %} | ||||||
|  | {% else %} | ||||||
|  |   {% extends "core/base.jinja" %} | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
| {% block title %} | {% block title %} | ||||||
|   {% if file %} |   {% if file %} | ||||||
| @@ -21,7 +25,7 @@ | |||||||
|   {% endif %} |   {% endif %} | ||||||
| {% endmacro %} | {% endmacro %} | ||||||
|  |  | ||||||
| {% block content %} | {% block tabs %} | ||||||
|   {{ print_file_name(file) }} |   {{ print_file_name(file) }} | ||||||
|  |  | ||||||
|   <div class="tool_bar"> |   <div class="tool_bar"> | ||||||
| @@ -44,6 +48,9 @@ | |||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|   <hr> |   <hr> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  |  | ||||||
|   {% if file %} |   {% if file %} | ||||||
|     {% block file %} |     {% block file %} | ||||||
|   | |||||||
| @@ -4,15 +4,49 @@ | |||||||
|   {% trans %}Delete confirmation{% endtrans %} |   {% trans %}Delete confirmation{% endtrans %} | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
|  | {% if is_fragment %} | ||||||
|  |  | ||||||
|  |   {# Don't display tabs and errors #} | ||||||
|  |   {% block tabs %} | ||||||
|  |   {% endblock %} | ||||||
|  |   {% block errors %} | ||||||
|  |   {% endblock %} | ||||||
|  |  | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
| {% block file %} | {% block file %} | ||||||
|   <h2>{% trans %}Delete confirmation{% endtrans %}</h2> |   <h2>{% trans %}Delete confirmation{% endtrans %}</h2> | ||||||
|   <form action="" method="post">{% csrf_token %} |  | ||||||
|  |   {% if next %} | ||||||
|  |     {% set action = current + "?next=" + next %} | ||||||
|  |   {% else %} | ||||||
|  |     {% set action = current %} | ||||||
|  |   {% endif %} | ||||||
|  |  | ||||||
|  |   <form action="{{ action }}" method="post"> | ||||||
|  |     {% csrf_token %} | ||||||
|  |  | ||||||
|     <p>{% trans obj=object %}Are you sure you want to delete "{{ obj }}"?{% endtrans %}</p> |     <p>{% trans obj=object %}Are you sure you want to delete "{{ obj }}"?{% endtrans %}</p> | ||||||
|     <input type="submit" value="{% trans %}Confirm{% endtrans %}" /> |     <button | ||||||
|   </form> |       {% if is_fragment %} | ||||||
|   <form method="GET" action="javascript:history.back();"> |         hx-post="{{ action }}" | ||||||
|     <input type="submit" name="cancel" value="{% trans %}Cancel{% endtrans %}" /> |         hx-target="#content" | ||||||
|  |         hx-swap="outerHtml" | ||||||
|  |       {% endif %} | ||||||
|  |     >{% trans %}Confirm{% endtrans %}</button> | ||||||
|  |  | ||||||
|  |     <button | ||||||
|  |       {% if is_fragment %} | ||||||
|  |         hx-get="{{ previous }}" | ||||||
|  |         hx-target="#content" | ||||||
|  |         hx-swap="outerHtml" | ||||||
|  |       {% else %} | ||||||
|  |         action="window.history.back()" | ||||||
|  |       {% endif %} | ||||||
|  |     >{% trans %}Cancel{% endtrans %}</button> | ||||||
|  |  | ||||||
|   </form> |   </form> | ||||||
|  |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,16 @@ | |||||||
| {% extends "core/base.jinja" %} | {% if is_fragment %} | ||||||
|  |   {% extends "core/base_fragment.jinja" %} | ||||||
|  |  | ||||||
|  |   {# Don't display tabs and errors #} | ||||||
|  |   {% block tabs %} | ||||||
|  |   {% endblock %} | ||||||
|  |   {% block errors %} | ||||||
|  |   {% endblock %} | ||||||
|  | {% else %} | ||||||
|  |   {% extends "core/base.jinja" %} | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
|  | {% from "core/macros.jinja" import paginate_htmx %} | ||||||
|  |  | ||||||
| {% block title %} | {% block title %} | ||||||
|   {% trans %}File moderation{% endtrans %} |   {% trans %}File moderation{% endtrans %} | ||||||
| @@ -7,8 +19,11 @@ | |||||||
| {% block content %} | {% block content %} | ||||||
|   <h3>{% trans %}File moderation{% endtrans %}</h3> |   <h3>{% trans %}File moderation{% endtrans %}</h3> | ||||||
|   <div> |   <div> | ||||||
|     {% for f in files %} |     {% for f in object_list %} | ||||||
|       <div style="margin: 2px; padding: 2px; border: solid 1px red; text-align: center"> |       <div | ||||||
|  |         id="file-{{ loop.index }}" | ||||||
|  |         style="margin: 2px; padding: 2px; border: solid 1px red; text-align: center" | ||||||
|  |       > | ||||||
|         {% if f.is_folder %} |         {% if f.is_folder %} | ||||||
|           <strong>Folder</strong> |           <strong>Folder</strong> | ||||||
|         {% else %} |         {% else %} | ||||||
| @@ -20,9 +35,19 @@ | |||||||
|           {% trans %}Owner: {% endtrans %}{{ f.owner.get_display_name() }}<br/> |           {% trans %}Owner: {% endtrans %}{{ f.owner.get_display_name() }}<br/> | ||||||
|           {% trans %}Date: {% endtrans %}{{ f.date|date(DATE_FORMAT) }} {{ f.date|time(TIME_FORMAT) }}<br/> |           {% trans %}Date: {% endtrans %}{{ f.date|date(DATE_FORMAT) }} {{ f.date|time(TIME_FORMAT) }}<br/> | ||||||
|         </p> |         </p> | ||||||
|         <p><a href="{{ url('core:file_moderate', file_id=f.id) }}">{% trans %}Moderate{% endtrans %}</a> - |         <p><button | ||||||
|           <a href="{{ url('core:file_delete', file_id=f.id) }}?next={{ url('core:file_moderation') }}">{% trans %}Delete{% endtrans %}</a></p> |           hx-get="{{ url('core:file_moderate', file_id=f.id) }}" | ||||||
|  |           hx-target="#content" | ||||||
|  |           hx-swap="outerHtml" | ||||||
|  |         >{% trans %}Moderate{% endtrans %}</button> - | ||||||
|  |           {% set current_page = url('core:file_moderation') + "?page=" + page_obj.number | string %} | ||||||
|  |           <button | ||||||
|  |             hx-get="{{ url('core:file_delete', file_id=f.id) }}?next={{ current_page | urlencode }}&previous={{ current_page | urlencode }}" | ||||||
|  |             hx-target="#file-{{ loop.index }}" | ||||||
|  |             hx-swap="outerHtml" | ||||||
|  |           >{% trans %}Delete{% endtrans %}</button></p> | ||||||
|       </div> |       </div> | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|  |     {{ paginate_htmx(page_obj, paginator) }} | ||||||
|   </div> |   </div> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -166,9 +166,37 @@ | |||||||
|         current_page (django.core.paginator.Page): the current page object |         current_page (django.core.paginator.Page): the current page object | ||||||
|         paginator (django.core.paginator.Paginator): the paginator object |         paginator (django.core.paginator.Paginator): the paginator object | ||||||
|     #} |     #} | ||||||
|  |   {{  paginate_server_side(current_page, paginator, False) }} | ||||||
|  | {% endmacro %} | ||||||
|  |  | ||||||
|  | {% macro paginate_htmx(current_page, paginator) %} | ||||||
|  |     {# Add pagination buttons for pages without Alpine but supporting framgents. | ||||||
|  |  | ||||||
|  |     This must be coupled with a view that handles pagination | ||||||
|  |     with the Django Paginator object and supports framgents. | ||||||
|  |  | ||||||
|  |     The relpaced fragment will be #content so make sure you are calling this macro inside your content block. | ||||||
|  |  | ||||||
|  |     Parameters: | ||||||
|  |         current_page (django.core.paginator.Page): the current page object | ||||||
|  |         paginator (django.core.paginator.Paginator): the paginator object | ||||||
|  |     #} | ||||||
|  |   {{  paginate_server_side(current_page, paginator, True) }} | ||||||
|  | {% endmacro %} | ||||||
|  |  | ||||||
|  | {% macro paginate_server_side(current_page, paginator, use_htmx) %} | ||||||
|   <nav class="pagination"> |   <nav class="pagination"> | ||||||
|     {% if current_page.has_previous() %} |     {% if current_page.has_previous() %} | ||||||
|       <a href="?page={{ current_page.previous_page_number() }}"> |       <a | ||||||
|  |         {% if use_htmx -%} | ||||||
|  |           hx-get="?page={{ current_page.previous_page_number() }}" | ||||||
|  |           hx-swap="innerHTML" | ||||||
|  |           hx-target="#content" | ||||||
|  |           hx-push-url="true" | ||||||
|  |         {%- else -%} | ||||||
|  |           href="?page={{ current_page.previous_page_number() }}" | ||||||
|  |         {%- endif -%} | ||||||
|  |       > | ||||||
|         <button> |         <button> | ||||||
|           <i class="fa fa-caret-left"></i> |           <i class="fa fa-caret-left"></i> | ||||||
|         </button> |         </button> | ||||||
| @@ -182,16 +210,33 @@ | |||||||
|       {% elif i == paginator.ELLIPSIS %} |       {% elif i == paginator.ELLIPSIS %} | ||||||
|         <strong>{{ paginator.ELLIPSIS }}</strong> |         <strong>{{ paginator.ELLIPSIS }}</strong> | ||||||
|       {% else %} |       {% else %} | ||||||
|         <a href="?page={{ i }}"> |         <a | ||||||
|  |           {% if use_htmx -%} | ||||||
|  |             hx-get="?page={{ i }}" | ||||||
|  |             hx-swap="innerHTML" | ||||||
|  |             hx-target="#content" | ||||||
|  |             hx-push-url="true" | ||||||
|  |           {%- else -%} | ||||||
|  |             href="?page={{ i }}" | ||||||
|  |           {%- endif -%} | ||||||
|  |         > | ||||||
|           <button>{{ i }}</button> |           <button>{{ i }}</button> | ||||||
|         </a> |         </a> | ||||||
|       {% endif %} |       {% endif %} | ||||||
|     {% endfor %} |     {% endfor %} | ||||||
|     {% if current_page.has_next() %} |     {% if current_page.has_next() %} | ||||||
|       <a href="?page={{ current_page.next_page_number() }}"> |       <a | ||||||
|         <button> |         {% if use_htmx -%} | ||||||
|           <i class="fa fa-caret-right"></i> |           hx-get="?page={{ current_page.next_page_number() }}" | ||||||
|         </button> |           hx-swap="innerHTML" | ||||||
|  |           hx-target="#content" | ||||||
|  |           hx-push-url="true" | ||||||
|  |         {%- else -%} | ||||||
|  |           href="?page={{ current_page.next_page_number() }}" | ||||||
|  |         {%- endif -%} | ||||||
|  |       ><button> | ||||||
|  |         <i class="fa fa-caret-right"></i> | ||||||
|  |       </button> | ||||||
|       </a> |       </a> | ||||||
|     {% else %} |     {% else %} | ||||||
|       <button disabled="disabled"><i class="fa fa-caret-right"></i></button> |       <button disabled="disabled"><i class="fa fa-caret-right"></i></button> | ||||||
|   | |||||||
| @@ -77,7 +77,7 @@ | |||||||
|       {% set default_picture = this_picture.get_download_url()|tojson %} |       {% set default_picture = this_picture.get_download_url()|tojson %} | ||||||
|       {% set delete_url = ( |       {% set delete_url = ( | ||||||
|         url('core:file_delete', file_id=this_picture.id, popup='') |         url('core:file_delete', file_id=this_picture.id, popup='') | ||||||
|         +"?next=" + profile.get_absolute_url() |         + "?next=" + url('core:user_edit', user_id=profile.id) | ||||||
|       )|tojson %} |       )|tojson %} | ||||||
|     {%- else -%} |     {%- else -%} | ||||||
|       {% set default_picture = static('core/img/unknown.jpg')|tojson %} |       {% set default_picture = static('core/img/unknown.jpg')|tojson %} | ||||||
|   | |||||||
| @@ -142,6 +142,30 @@ class TestFileHandling(TestCase): | |||||||
|         assert "ls</a>" in str(response.content) |         assert "ls</a>" in str(response.content) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.django_db | ||||||
|  | class TestFileModerationView: | ||||||
|  |     """Test access to file moderation view""" | ||||||
|  |  | ||||||
|  |     @pytest.mark.parametrize( | ||||||
|  |         ("user_factory", "status_code"), | ||||||
|  |         [ | ||||||
|  |             (lambda: None, 403),  # Anonymous user | ||||||
|  |             (lambda: baker.make(User, is_superuser=True), 200), | ||||||
|  |             (lambda: baker.make(User), 403), | ||||||
|  |             (lambda: subscriber_user.make(), 403), | ||||||
|  |             (lambda: old_subscriber_user.make(), 403), | ||||||
|  |             (lambda: board_user.make(), 403), | ||||||
|  |         ], | ||||||
|  |     ) | ||||||
|  |     def test_view_access( | ||||||
|  |         self, client: Client, user_factory: Callable[[], User | None], status_code: int | ||||||
|  |     ): | ||||||
|  |         user = user_factory() | ||||||
|  |         if user:  # if None, then it's an anonymous user | ||||||
|  |             client.force_login(user_factory()) | ||||||
|  |         assert client.get(reverse("core:file_moderation")).status_code == status_code | ||||||
|  |  | ||||||
|  |  | ||||||
| @pytest.mark.django_db | @pytest.mark.django_db | ||||||
| class TestUserProfilePicture: | class TestUserProfilePicture: | ||||||
|     """Test interactions with user's profile picture.""" |     """Test interactions with user's profile picture.""" | ||||||
|   | |||||||
| @@ -326,6 +326,12 @@ class DetailFormView(SingleObjectMixin, FormView): | |||||||
|         return super().get_object() |         return super().get_object() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class AllowFragment: | ||||||
|  |     def get_context_data(self, **kwargs): | ||||||
|  |         kwargs["is_fragment"] = self.request.headers.get("HX-Request", False) | ||||||
|  |         return super().get_context_data(**kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
| # F403: those star-imports would be hellish to refactor | # F403: those star-imports would be hellish to refactor | ||||||
| # E402: putting those import at the top of the file would also be difficult | # E402: putting those import at the top of the file would also be difficult | ||||||
| from .files import *  # noqa: F403 E402 | from .files import *  # noqa: F403 E402 | ||||||
|   | |||||||
| @@ -27,12 +27,13 @@ from django.shortcuts import get_object_or_404, redirect | |||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| from django.utils.http import http_date | from django.utils.http import http_date | ||||||
| from django.utils.translation import gettext_lazy as _ | from django.utils.translation import gettext_lazy as _ | ||||||
| from django.views.generic import DetailView, ListView, TemplateView | from django.views.generic import DetailView, ListView | ||||||
| from django.views.generic.detail import SingleObjectMixin | from django.views.generic.detail import SingleObjectMixin | ||||||
| from django.views.generic.edit import DeleteView, FormMixin, UpdateView | from django.views.generic.edit import DeleteView, FormMixin, UpdateView | ||||||
|  |  | ||||||
| from core.models import Notification, RealGroup, SithFile | from core.models import Notification, RealGroup, SithFile, User | ||||||
| from core.views import ( | from core.views import ( | ||||||
|  |     AllowFragment, | ||||||
|     CanEditMixin, |     CanEditMixin, | ||||||
|     CanEditPropMixin, |     CanEditPropMixin, | ||||||
|     CanViewMixin, |     CanViewMixin, | ||||||
| @@ -352,7 +353,7 @@ class FileView(CanViewMixin, DetailView, FormMixin): | |||||||
|         return kwargs |         return kwargs | ||||||
|  |  | ||||||
|  |  | ||||||
| class FileDeleteView(CanEditPropMixin, DeleteView): | class FileDeleteView(AllowFragment, CanEditPropMixin, DeleteView): | ||||||
|     model = SithFile |     model = SithFile | ||||||
|     pk_url_kwarg = "file_id" |     pk_url_kwarg = "file_id" | ||||||
|     template_name = "core/file_delete_confirm.jinja" |     template_name = "core/file_delete_confirm.jinja" | ||||||
| @@ -376,19 +377,24 @@ class FileDeleteView(CanEditPropMixin, DeleteView): | |||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs): |     def get_context_data(self, **kwargs): | ||||||
|         kwargs = super().get_context_data(**kwargs) |         kwargs = super().get_context_data(**kwargs) | ||||||
|         kwargs["popup"] = "" |         kwargs["popup"] = "" if self.kwargs.get("popup") is None else "popup" | ||||||
|         if self.kwargs.get("popup") is not None: |         kwargs["next"] = self.request.GET.get("next", None) | ||||||
|             kwargs["popup"] = "popup" |         kwargs["previous"] = self.request.GET.get("previous", None) | ||||||
|  |         kwargs["current"] = self.request.path | ||||||
|         return kwargs |         return kwargs | ||||||
|  |  | ||||||
|  |  | ||||||
| class FileModerationView(TemplateView): | class FileModerationView(AllowFragment, ListView): | ||||||
|  |     model = SithFile | ||||||
|     template_name = "core/file_moderation.jinja" |     template_name = "core/file_moderation.jinja" | ||||||
|  |     queryset = SithFile.objects.filter(is_moderated=False, is_in_sas=False) | ||||||
|  |     paginate_by = 100 | ||||||
|  |  | ||||||
|     def get_context_data(self, **kwargs): |     def dispatch(self, request: HttpRequest, *args, **kwargs): | ||||||
|         kwargs = super().get_context_data(**kwargs) |         user: User = request.user | ||||||
|         kwargs["files"] = SithFile.objects.filter(is_moderated=False)[:100] |         if user.is_root: | ||||||
|         return kwargs |             return super().dispatch(request, *args, **kwargs) | ||||||
|  |         raise PermissionDenied() | ||||||
|  |  | ||||||
|  |  | ||||||
| class FileModerateView(CanEditPropMixin, SingleObjectMixin): | class FileModerateView(CanEditPropMixin, SingleObjectMixin): | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -22,6 +22,7 @@ | |||||||
|         "d3-force-3d": "^3.0.5", |         "d3-force-3d": "^3.0.5", | ||||||
|         "easymde": "^2.18.0", |         "easymde": "^2.18.0", | ||||||
|         "glob": "^11.0.0", |         "glob": "^11.0.0", | ||||||
|  |         "htmx.org": "^2.0.3", | ||||||
|         "jquery": "^3.7.1", |         "jquery": "^3.7.1", | ||||||
|         "jquery-ui": "^1.14.0", |         "jquery-ui": "^1.14.0", | ||||||
|         "jquery.shorten": "^1.0.0", |         "jquery.shorten": "^1.0.0", | ||||||
| @@ -4455,6 +4456,11 @@ | |||||||
|       "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", |       "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/htmx.org": { | ||||||
|  |       "version": "2.0.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.3.tgz", | ||||||
|  |       "integrity": "sha512-AeoJUAjkCVVajbfKX+3sVQBTCt8Ct4lif1T+z/tptTXo8+8yyq3QIMQQe/IT+R8ssfrO1I0DeX4CAronzCL6oA==" | ||||||
|  |     }, | ||||||
|     "node_modules/human-signals": { |     "node_modules/human-signals": { | ||||||
|       "version": "5.0.0", |       "version": "5.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", |       "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", | ||||||
|   | |||||||
| @@ -54,6 +54,7 @@ | |||||||
|     "d3-force-3d": "^3.0.5", |     "d3-force-3d": "^3.0.5", | ||||||
|     "easymde": "^2.18.0", |     "easymde": "^2.18.0", | ||||||
|     "glob": "^11.0.0", |     "glob": "^11.0.0", | ||||||
|  |     "htmx.org": "^2.0.3", | ||||||
|     "jquery": "^3.7.1", |     "jquery": "^3.7.1", | ||||||
|     "jquery-ui": "^1.14.0", |     "jquery-ui": "^1.14.0", | ||||||
|     "jquery.shorten": "^1.0.0", |     "jquery.shorten": "^1.0.0", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user