mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-10-31 09:03:06 +00:00 
			
		
		
		
	Merge pull request #1017 from ae-utbm/subscription-perms
Subscription perms
This commit is contained in:
		| @@ -125,6 +125,11 @@ class Command(BaseCommand): | ||||
|             unix_name=settings.SITH_MAIN_CLUB["unix_name"], | ||||
|             address=settings.SITH_MAIN_CLUB["address"], | ||||
|         ) | ||||
|         main_club.board_group.permissions.add( | ||||
|             *Permission.objects.filter( | ||||
|                 codename__in=["view_subscription", "add_subscription"] | ||||
|             ) | ||||
|         ) | ||||
|         bar_club = Club.objects.create( | ||||
|             id=2, | ||||
|             name=settings.SITH_BAR_MANAGER["name"], | ||||
|   | ||||
| @@ -417,29 +417,6 @@ class User(AbstractUser): | ||||
|     def is_board_member(self) -> bool: | ||||
|         return self.groups.filter(club_board=settings.SITH_MAIN_CLUB_ID).exists() | ||||
|  | ||||
|     @cached_property | ||||
|     def can_read_subscription_history(self) -> bool: | ||||
|         if self.is_root or self.is_board_member: | ||||
|             return True | ||||
|  | ||||
|         from club.models import Club | ||||
|  | ||||
|         for club in Club.objects.filter( | ||||
|             id__in=settings.SITH_CAN_READ_SUBSCRIPTION_HISTORY | ||||
|         ): | ||||
|             if club in self.clubs_with_rights: | ||||
|                 return True | ||||
|         return False | ||||
|  | ||||
|     @cached_property | ||||
|     def can_create_subscription(self) -> bool: | ||||
|         return self.is_root or ( | ||||
|             self.memberships.board() | ||||
|             .ongoing() | ||||
|             .filter(club_id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS) | ||||
|             .exists() | ||||
|         ) | ||||
|  | ||||
|     @cached_property | ||||
|     def is_launderette_manager(self): | ||||
|         from club.models import Club | ||||
| @@ -679,14 +656,6 @@ class AnonymousUser(AuthAnonymousUser): | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
|  | ||||
|     @property | ||||
|     def can_create_subscription(self): | ||||
|         return False | ||||
|  | ||||
|     @property | ||||
|     def can_read_subscription_history(self): | ||||
|         return False | ||||
|  | ||||
|     @property | ||||
|     def was_subscribed(self): | ||||
|         return False | ||||
|   | ||||
| @@ -1,19 +1,40 @@ | ||||
| {% extends "core/base.jinja" %} | ||||
|  | ||||
| {# if the template context has the `object_name` variable, | ||||
|    then this one will be used in the page title, | ||||
|    instead of the result of `str(object)` #} | ||||
| {% if object and not object_name %} | ||||
|   {% set object_name=object %} | ||||
| {% endif %} | ||||
|  | ||||
| {% block title %} | ||||
|   {% if object %} | ||||
|     {% trans obj=object %}Edit {{ obj }}{% endtrans %} | ||||
|   {% if object_name %} | ||||
|     {% trans name=object_name %}Edit {{ name }}{% endtrans %} | ||||
|   {% else %} | ||||
|     {% trans %}Save{% endtrans %} | ||||
|   {% endif %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block content %} | ||||
|   {% if object %} | ||||
|     <h2>{% trans obj=object %}Edit {{ obj }}{% endtrans %}</h2> | ||||
|   {% if object_name %} | ||||
|     <h2>{% trans name=object_name %}Edit {{ name }}{% endtrans %}</h2> | ||||
|   {% else %} | ||||
|     <h2>{% trans %}Save{% endtrans %}</h2> | ||||
|   {% endif %} | ||||
|   {% if messages %} | ||||
|     <div x-data="{show_alert: true}" class="alert alert-green" x-show="show_alert" x-transition> | ||||
|       <span class="alert-main"> | ||||
|         {% for message in messages %} | ||||
|           {% if message.level_tag == "success" %} | ||||
|             {{ message }} | ||||
|           {% endif %} | ||||
|         {% endfor %} | ||||
|       </span> | ||||
|       <span class="clickable" @click="show_alert = false"> | ||||
|         <i class="fa fa-close"></i> | ||||
|       </span> | ||||
|     </div> | ||||
|   {% endif %} | ||||
|   <form action="" method="post" enctype="multipart/form-data"> | ||||
|     {% csrf_token %} | ||||
|     {{ form.as_p() }} | ||||
|   | ||||
| @@ -166,7 +166,7 @@ | ||||
|   </div> | ||||
| {% endif %} | ||||
| <br> | ||||
| {% if profile.was_subscribed and (user == profile or user.can_read_subscription_history)%} | ||||
| {% if profile.was_subscribed and (user == profile or user.has_perm("subscription.view_subscription")) %} | ||||
|   <div class="collapse" :class="{'shadow': collapsed}" x-data="{collapsed: false}" x-cloak> | ||||
|     <div class="collapse-header clickable" @click="collapsed = !collapsed"> | ||||
|       <span class="collapse-header-text"> | ||||
| @@ -197,9 +197,9 @@ | ||||
|       </table> | ||||
|     </div> | ||||
|   </div> | ||||
|   <hr> | ||||
| {% endif %} | ||||
|  | ||||
| <hr> | ||||
| <div> | ||||
|   {% if user.is_root or user.is_board_member %} | ||||
|     <form class="form-gifts" action="{{ url('core:user_gift_create', user_id=profile.id) }}" method="post"> | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
|     <h3>{% trans %}User Tools{% endtrans %}</h3> | ||||
|  | ||||
|     <div class="container"> | ||||
|       {% if user.can_create_subscription or user.is_root or user.is_board_member %} | ||||
|       {% if user.has_perm("subscription.view_userban") or user.is_root or user.is_board_member %} | ||||
|         <div> | ||||
|           <h4>{% trans %}Sith management{% endtrans %}</h4> | ||||
|           <ul> | ||||
| @@ -21,16 +21,16 @@ | ||||
|               <li><a href="{{ url('core:group_list') }}">{% trans %}Groups{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('rootplace:merge') }}">{% trans %}Merge users{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('rootplace:operation_logs') }}">{% trans %}Operation logs{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('rootplace:delete_forum_messages') }}">{% trans %}Delete user's forum messages{% endtrans %}</a></li> | ||||
|               <li> | ||||
|                 <a href="{{ url('rootplace:delete_forum_messages') }}"> | ||||
|                   {% trans %}Delete user's forum messages{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|             {% endif %} | ||||
|             {% if user.has_perm("core.view_userban") %} | ||||
|               <li><a href="{{ url("rootplace:ban_list") }}">{% trans %}Bans{% endtrans %}</a></li> | ||||
|             {% endif %} | ||||
|             {% if user.can_create_subscription or user.is_root %} | ||||
|               <li><a href="{{ url('subscription:subscription') }}">{% trans %}Subscriptions{% endtrans %}</a></li> | ||||
|             {% endif %} | ||||
|             {% if user.is_board_member or user.is_root %} | ||||
|               <li><a href="{{ url('subscription:stats') }}">{% trans %}Subscription stats{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('club:club_new') }}">{% trans %}New club{% endtrans %}</a></li> | ||||
|             {% endif %} | ||||
|           </ul> | ||||
| @@ -42,152 +42,202 @@ | ||||
|         {% set is_admin_on_a_counter = true %} | ||||
|       {% endfor %} | ||||
|  | ||||
|       {% if | ||||
|       is_admin_on_a_counter | ||||
|       or user.is_root | ||||
|       or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID) | ||||
|       %} | ||||
|       {% if is_admin_on_a_counter or user.is_root or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID) %} | ||||
|         <div> | ||||
|           <h4>{% trans %}Counters{% endtrans %}</h4> | ||||
|           <ul> | ||||
|             {% if user.is_root or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID) %} | ||||
|               <li> | ||||
|                 <a href="{{ url('counter:admin_list') }}"> | ||||
|                   {% trans %}General counters management{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|               <li> | ||||
|                 <a href="{{ url('counter:product_list') }}"> | ||||
|                   {% trans %}Products management{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|               <li> | ||||
|                 <a href="{{ url('counter:product_type_list') }}"> | ||||
|                   {% trans %}Product types management{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|               <li> | ||||
|                 <a href="{{ url('counter:cash_summary_list') }}"> | ||||
|                   {% trans %}Cash register summaries{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|               <li> | ||||
|                 <a href="{{ url('counter:invoices_call') }}"> | ||||
|                   {% trans %}Invoices call{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|               <li> | ||||
|                 <a href="{{ url('counter:eticket_list') }}"> | ||||
|                   {% trans %}Etickets{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|             {% endif %} | ||||
|           </ul> | ||||
|           <ul> | ||||
|             {% for b in settings.SITH_COUNTER_BARS %} | ||||
|               {% if user.is_in_group(name=b[1]+" admin") %} | ||||
|                 {% set c = Counter.objects.filter(id=b[0]).first() %} | ||||
|  | ||||
|                 <li class="rows counter"> | ||||
|                   <a href="{{ url('counter:details', counter_id=b[0]) }}">{{ b[1] }}</a> | ||||
|  | ||||
|                   <span> | ||||
|                     <span> | ||||
|                       <a class="button" href="{{ url('counter:admin', counter_id=b[0]) }}"> | ||||
|                         {% trans %}Edit{% endtrans %} | ||||
|                       </a> | ||||
|                       <a class="button" href="{{ url('counter:stats', counter_id=b[0]) }}"> | ||||
|                         {% trans %}Stats{% endtrans %} | ||||
|                       </a> | ||||
|                     </span> | ||||
|                   </span> | ||||
|                 </li> | ||||
|               {% endif %} | ||||
|             {% endfor %} | ||||
|           </ul> | ||||
|         </div> | ||||
|       {% endif %} | ||||
|  | ||||
|       {% if user.is_root or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %} | ||||
|         <div> | ||||
|           <h4>{% trans %}Accounting{% endtrans %}</h4> | ||||
|           <ul> | ||||
|             {% if user.is_root or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %} | ||||
|               <li><a href="{{ url('accounting:refound_account') }}">{% trans %}Refound Account{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('accounting:bank_list') }}">{% trans %}General accounting{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('accounting:co_list') }}">{% trans %}Company list{% endtrans %}</a></li> | ||||
|             {% endif %} | ||||
|  | ||||
|             {% for m in user.memberships.filter(end_date=None).filter(role__gte=7).all() -%} | ||||
|               {%- for b in m.club.bank_accounts.all() %} | ||||
|                 <li class="rows"> | ||||
|                   <strong>{% trans %}Bank account: {% endtrans %}</strong> | ||||
|                   <a href="{{ url('accounting:bank_details', b_account_id=b.id) }}">{{ b }}</a> | ||||
|                 </li> | ||||
|               {%- endfor %} | ||||
|  | ||||
|               {% if m.club.club_account.exists() -%} | ||||
|                 {% for ca in m.club.club_account.all() %} | ||||
|                   <li class="rows"> | ||||
|                     <strong>{% trans %}Club account: {% endtrans %}</strong> | ||||
|                     <a href="{{ url('accounting:club_details', c_account_id=ca.id) }}">{{ ca }}</a> | ||||
|                   </li> | ||||
|                 {%- endfor %} | ||||
|               {%- endif -%} | ||||
|             {%- endfor %} | ||||
|           </ul> | ||||
|         </div> | ||||
|       {% endif %} | ||||
|  | ||||
|       {% if user.is_root or user.is_com_admin or user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID) %} | ||||
|         <div> | ||||
|           <h4>{% trans %}Communication{% endtrans %}</h4> | ||||
|           <ul> | ||||
|             {% if user.is_com_admin or user.is_root %} | ||||
|               <li><a href="{{ url('com:weekmail_article') }}">{% trans %}Create weekmail article{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('com:weekmail') }}">{% trans %}Weekmail{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('com:weekmail_destinations') }}">{% trans %}Weekmail destinations{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('com:news_new') }}">{% trans %}Create news{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('com:news_admin_list') }}">{% trans %}Moderate news{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('com:alert_edit') }}">{% trans %}Edit alert message{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('com:info_edit') }}">{% trans %}Edit information message{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('core:file_moderation') }}">{% trans %}Moderate files{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('com:mailing_admin') }}">{% trans %}Mailing lists administration{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('com:poster_list') }}">{% trans %}Posters{% endtrans %}</a></li> | ||||
|               <li><a href="{{ url('com:screen_list') }}">{% trans %}Screens{% endtrans %}</a></li> | ||||
|             {% endif %} | ||||
|             {% if user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID) %} | ||||
|               <li><a href="{{ url('sas:moderation') }}">{% trans %}Moderate pictures{% endtrans %}</a></li> | ||||
|             {% endif %} | ||||
|           </ul> | ||||
|         </div> | ||||
|       {% endif %} | ||||
|  | ||||
|       {% if user.has_perm("subscription.add_subscription") or user.has_perm("auth.change_perm") or user.is_root or user.is_board_member %} | ||||
|         <div> | ||||
|           <h4>{% trans %}Subscriptions{% endtrans %}</h4> | ||||
|           <ul> | ||||
|             {% if user.has_perm("subscription.add_subscription") %} | ||||
|               <li> | ||||
|                 <a href="{{ url("subscription:subscription") }}"> | ||||
|                   {% trans %}New subscription{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|             {% endif %} | ||||
|             {% if user.has_perm("auth.change_permission") %} | ||||
|               <li> | ||||
|                 <a href="{{ url("subscription:perms") }}"> | ||||
|                   {% trans %}Manage permissions{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|             {% endif %} | ||||
|             {% if user.is_root or user.is_board_member %} | ||||
|               <li> | ||||
|                 <a href="{{ url("subscription:stats") }}"> | ||||
|                   {% trans %}Subscription stats{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|             {% endif %} | ||||
|           </ul> | ||||
|         </div> | ||||
|       {% endif %} | ||||
|  | ||||
|       {% if user.memberships.filter(end_date=None).all().count() > 0 %} | ||||
|         <div> | ||||
|           <h4>{% trans %}Club tools{% endtrans %}</h4> | ||||
|           <ul> | ||||
|             {% for m in user.memberships.filter(end_date=None).all() %} | ||||
|               <li><a href="{{ url('club:tools', club_id=m.club.id) }}">{{ m.club }}</a></li> | ||||
|             {% endfor %} | ||||
|           </ul> | ||||
|         </div> | ||||
|       {% endif %} | ||||
|  | ||||
|       {% if user.has_perm("pedagogy.add_uv") or user.has_perm("pedagogy.delete_uvcomment") %} | ||||
|         <div> | ||||
|           <h4>{% trans %}Pedagogy{% endtrans %}</h4> | ||||
|           <ul> | ||||
|             {% if user.has_perm("pedagogy.add_uv") %} | ||||
|               <li> | ||||
|                 <a href="{{ url("pedagogy:uv_create") }}"> | ||||
|                   {% trans %}Create UV{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|             {% endif %} | ||||
|             {% if user.has_perm("pedagogy.delete_uvcomment") %} | ||||
|               <li> | ||||
|                 <a href="{{ url("pedagogy:moderation") }}"> | ||||
|                   {% trans %}Moderate comments{% endtrans %} | ||||
|                 </a> | ||||
|               </li> | ||||
|             {% endif %} | ||||
|           </ul> | ||||
|         </div> | ||||
|       {% endif %} | ||||
|  | ||||
|       <div> | ||||
|         <h4>{% trans %}Counters{% endtrans %}</h4> | ||||
|         <h4>{% trans %}Elections{% endtrans %}</h4> | ||||
|         <ul> | ||||
|           {% if user.is_root | ||||
|           or user.is_in_group(pk=settings.SITH_GROUP_COUNTER_ADMIN_ID) | ||||
|           %} | ||||
|           <li><a href="{{ url('counter:admin_list') }}">{% trans %}General counters management{% endtrans %}</a></li> | ||||
|           <li><a href="{{ url('counter:product_list') }}">{% trans %}Products management{% endtrans %}</a></li> | ||||
|           <li><a href="{{ url('counter:product_type_list') }}">{% trans %}Product types management{% endtrans %}</a></li> | ||||
|           <li><a href="{{ url('counter:cash_summary_list') }}">{% trans %}Cash register summaries{% endtrans %}</a></li> | ||||
|           <li><a href="{{ url('counter:invoices_call') }}">{% trans %}Invoices call{% endtrans %}</a></li> | ||||
|           <li><a href="{{ url('counter:eticket_list') }}">{% trans %}Etickets{% endtrans %}</a></li> | ||||
| {% endif %} | ||||
| </ul> | ||||
| <ul> | ||||
|   {% for b in settings.SITH_COUNTER_BARS %} | ||||
|     {% if user.is_in_group(name=b[1]+" admin") %} | ||||
|       {% set c = Counter.objects.filter(id=b[0]).first() %} | ||||
|           <li><a href="{{ url('election:list') }}">{% trans %}See available elections{% endtrans %}</a></li> | ||||
|           <li><a href="{{ url('election:list_archived') }}">{% trans %}See archived elections{% endtrans %}</a></li> | ||||
|           {%- if user.is_subscribed -%} | ||||
|             <li><a href="{{ url('election:create') }}">{% trans %}Create a new election{% endtrans %}</a></li> | ||||
|           {%- endif -%} | ||||
|         </ul> | ||||
|       </div> | ||||
|  | ||||
|       <li class="rows counter"> | ||||
|         <a href="{{ url('counter:details', counter_id=b[0]) }}">{{ b[1] }}</a> | ||||
|  | ||||
|         <span> | ||||
|           <span> | ||||
|             <a class="button" href="{{ url('counter:admin', counter_id=b[0]) }}">{% trans %}Edit{% endtrans %}</a> | ||||
|             <a class="button" href="{{ url('counter:stats', counter_id=b[0]) }}">{% trans %}Stats{% endtrans %}</a> | ||||
|           </span> | ||||
|         </span> | ||||
|       </li> | ||||
|     {% endif %} | ||||
|   {% endfor %} | ||||
| </ul> | ||||
| </div> | ||||
| {% endif %} | ||||
|  | ||||
| {% if | ||||
| user.is_root | ||||
| or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) | ||||
| or user.memberships.ongoing().filter(role__gte=7).count() > 10 | ||||
| %} | ||||
| <div> | ||||
|   <h4>{% trans %}Accounting{% endtrans %}</h4> | ||||
|   <ul> | ||||
|     {% if user.is_root | ||||
|     or user.is_in_group(pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) | ||||
|     %} | ||||
|     <li><a href="{{ url('accounting:refound_account') }}">{% trans %}Refound Account{% endtrans %}</a></li> | ||||
|     <li><a href="{{ url('accounting:bank_list') }}">{% trans %}General accounting{% endtrans %}</a></li> | ||||
|     <li><a href="{{ url('accounting:co_list') }}">{% trans %}Company list{% endtrans %}</a></li> | ||||
|     {% endif %} | ||||
|  | ||||
|     {% for m in user.memberships.filter(end_date=None).filter(role__gte=7).all() -%} | ||||
|       {%- for b in m.club.bank_accounts.all() %} | ||||
|         <li class="rows"> | ||||
|           <strong>{% trans %}Bank account: {% endtrans %}</strong> | ||||
|           <a href="{{ url('accounting:bank_details', b_account_id=b.id) }}">{{ b }}</a> | ||||
|         </li> | ||||
|       {%- endfor %} | ||||
|  | ||||
|       {% if m.club.club_account.exists() -%} | ||||
|         {% for ca in m.club.club_account.all() %} | ||||
|           <li class="rows"> | ||||
|             <strong>{% trans %}Club account: {% endtrans %}</strong> | ||||
|             <a href="{{ url('accounting:club_details', c_account_id=ca.id) }}">{{ ca }}</a> | ||||
|           </li> | ||||
|         {%- endfor %} | ||||
|       {%- endif -%} | ||||
|     {%- endfor %} | ||||
|   </ul> | ||||
| </div> | ||||
| {% endif %} | ||||
|  | ||||
| {% if | ||||
| user.is_root | ||||
| or user.is_com_admin | ||||
| or user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID) | ||||
| %} | ||||
| <div> | ||||
|   <h4>{% trans %}Communication{% endtrans %}</h4> | ||||
|   <ul> | ||||
|     {% if user.is_com_admin or user.is_root %} | ||||
|       <li><a href="{{ url('com:weekmail_article') }}">{% trans %}Create weekmail article{% endtrans %}</a></li> | ||||
|       <li><a href="{{ url('com:weekmail') }}">{% trans %}Weekmail{% endtrans %}</a></li> | ||||
|       <li><a href="{{ url('com:weekmail_destinations') }}">{% trans %}Weekmail destinations{% endtrans %}</a></li> | ||||
|       <li><a href="{{ url('com:news_new') }}">{% trans %}Create news{% endtrans %}</a></li> | ||||
|       <li><a href="{{ url('com:news_admin_list') }}">{% trans %}Moderate news{% endtrans %}</a></li> | ||||
|       <li><a href="{{ url('com:alert_edit') }}">{% trans %}Edit alert message{% endtrans %}</a></li> | ||||
|       <li><a href="{{ url('com:info_edit') }}">{% trans %}Edit information message{% endtrans %}</a></li> | ||||
|       <li><a href="{{ url('core:file_moderation') }}">{% trans %}Moderate files{% endtrans %}</a></li> | ||||
|       <li><a href="{{ url('com:mailing_admin') }}">{% trans %}Mailing lists administration{% endtrans %}</a></li> | ||||
|       <li><a href="{{ url('com:poster_list') }}">{% trans %}Posters{% endtrans %}</a></li> | ||||
|       <li><a href="{{ url('com:screen_list') }}">{% trans %}Screens{% endtrans %}</a></li> | ||||
|     {% endif %} | ||||
|     {% if user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID) %} | ||||
|       <li><a href="{{ url('sas:moderation') }}">{% trans %}Moderate pictures{% endtrans %}</a></li> | ||||
|     {% endif %} | ||||
|   </ul> | ||||
| </div> | ||||
| {% endif %} | ||||
|  | ||||
| {% if user.memberships.filter(end_date=None).all().count() > 0 %} | ||||
|   <div> | ||||
|     <h4>{% trans %}Club tools{% endtrans %}</h4> | ||||
|     <ul> | ||||
|       {% for m in user.memberships.filter(end_date=None).all() %} | ||||
|         <li><a href="{{ url('club:tools', club_id=m.club.id) }}">{{ m.club }}</a></li> | ||||
|       {% endfor %} | ||||
|     </ul> | ||||
|   </div> | ||||
| {% endif %} | ||||
|  | ||||
| {% if | ||||
| user.is_root | ||||
| or user.is_in_group(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) | ||||
| %} | ||||
| <div> | ||||
|   <h4>{% trans %}Pedagogy{% endtrans %}</h4> | ||||
|   <ul> | ||||
|     <li><a href="{{ url('pedagogy:uv_create') }}">{% trans %}Create UV{% endtrans %}</a></li> | ||||
|     <li><a href="{{ url('pedagogy:moderation') }}">{% trans %}Moderate comments{% endtrans %}</a></li> | ||||
|   </ul> | ||||
| </div> | ||||
| {% endif %} | ||||
|  | ||||
| <div> | ||||
|   <h4>{% trans %}Elections{% endtrans %}</h4> | ||||
|   <ul> | ||||
|     <li><a href="{{ url('election:list') }}">{% trans %}See available elections{% endtrans %}</a></li> | ||||
|     <li><a href="{{ url('election:list_archived') }}">{% trans %}See archived elections{% endtrans %}</a></li> | ||||
|     {%- if user.is_subscribed -%} | ||||
|       <li><a href="{{ url('election:create') }}">{% trans %}Create a new election{% endtrans %}</a></li> | ||||
|     {%- endif -%} | ||||
|   </ul> | ||||
| </div> | ||||
|  | ||||
| <div> | ||||
|   <h4>{% trans %}Other tools{% endtrans %}</h4> | ||||
|   <ul> | ||||
|     <li><a href="{{ url('trombi:user_tools') }}">{% trans %}Trombi tools{% endtrans %}</a></li> | ||||
|   </ul> | ||||
| </div> | ||||
| </div> | ||||
| </main> | ||||
|       <div> | ||||
|         <h4>{% trans %}Other tools{% endtrans %}</h4> | ||||
|         <ul> | ||||
|           <li><a href="{{ url('trombi:user_tools') }}">{% trans %}Trombi tools{% endtrans %}</a></li> | ||||
|         </ul> | ||||
|       </div> | ||||
|     </div> | ||||
|   </main> | ||||
| {% endblock %} | ||||
| @@ -28,6 +28,7 @@ from captcha.fields import CaptchaField | ||||
| from django import forms | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.forms import AuthenticationForm, UserCreationForm | ||||
| from django.contrib.auth.models import Permission | ||||
| from django.contrib.staticfiles.management.commands.collectstatic import ( | ||||
|     staticfiles_storage, | ||||
| ) | ||||
| @@ -440,3 +441,28 @@ class GiftForm(forms.ModelForm): | ||||
|                 id=user_id | ||||
|             ) | ||||
|             self.fields["user"].widget = forms.HiddenInput() | ||||
|  | ||||
|  | ||||
| class PermissionGroupsForm(forms.ModelForm): | ||||
|     """Manage the groups that have a specific permission.""" | ||||
|  | ||||
|     class Meta: | ||||
|         model = Permission | ||||
|         fields = [] | ||||
|  | ||||
|     groups = forms.ModelMultipleChoiceField( | ||||
|         Group.objects.all(), | ||||
|         label=_("Groups"), | ||||
|         widget=AutoCompleteSelectMultipleGroup, | ||||
|         required=False, | ||||
|     ) | ||||
|  | ||||
|     def __init__(self, instance: Permission, **kwargs): | ||||
|         super().__init__(instance=instance, **kwargs) | ||||
|         self.fields["groups"].initial = instance.group_set.all() | ||||
|  | ||||
|     def save(self, commit: bool = True):  # noqa FTB001 | ||||
|         instance = super().save(commit=False) | ||||
|         if commit: | ||||
|             instance.group_set.set(self.cleaned_data["groups"]) | ||||
|         return instance | ||||
|   | ||||
| @@ -17,6 +17,10 @@ | ||||
|  | ||||
| from django import forms | ||||
| from django.contrib.auth.mixins import PermissionRequiredMixin | ||||
| from django.contrib.auth.models import Permission | ||||
| from django.contrib.messages.views import SuccessMessageMixin | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| from django.shortcuts import get_object_or_404 | ||||
| from django.urls import reverse_lazy | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.views.generic import ListView | ||||
| @@ -25,6 +29,7 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView | ||||
| from core.auth.mixins import CanEditMixin | ||||
| from core.models import Group, User | ||||
| from core.views import DetailFormView | ||||
| from core.views.forms import PermissionGroupsForm | ||||
| from core.views.widgets.select import AutoCompleteSelectMultipleUser | ||||
|  | ||||
| # Forms | ||||
| @@ -130,3 +135,62 @@ class GroupDeleteView(CanEditMixin, DeleteView): | ||||
|     pk_url_kwarg = "group_id" | ||||
|     template_name = "core/delete_confirm.jinja" | ||||
|     success_url = reverse_lazy("core:group_list") | ||||
|  | ||||
|  | ||||
| class PermissionGroupsUpdateView( | ||||
|     PermissionRequiredMixin, SuccessMessageMixin, UpdateView | ||||
| ): | ||||
|     """Manage the groups that have a specific permission. | ||||
|  | ||||
|     Notes: | ||||
|         This is an `UpdateView`, but unlike typical `UpdateView`, | ||||
|         it doesn't accept url arguments to retrieve the object | ||||
|         to update. | ||||
|         As such, a `PermissionGroupsUpdateView` can only deal with | ||||
|         a single hardcoded permission. | ||||
|  | ||||
|         This is not a limitation, but an on-purpose design, | ||||
|         mainly for security matters. | ||||
|  | ||||
|     Example: | ||||
|         ```python | ||||
|         class SubscriptionPermissionView(PermissionGroupsUpdateView): | ||||
|             permission = "subscription.add_subscription" | ||||
|         ``` | ||||
|     """ | ||||
|  | ||||
|     permission_required = "auth.change_permission" | ||||
|     template_name = "core/edit.jinja" | ||||
|     form_class = PermissionGroupsForm | ||||
|     permission = None | ||||
|     success_message = _("Groups have been successfully updated.") | ||||
|  | ||||
|     def get_object(self, *args, **kwargs): | ||||
|         if not self.permission: | ||||
|             raise ImproperlyConfigured( | ||||
|                 f"{self.__class__.__name__} is missing the permission attribute. " | ||||
|                 "Please fill it with either a permission string " | ||||
|                 "or a Permission object." | ||||
|             ) | ||||
|         if isinstance(self.permission, Permission): | ||||
|             return self.permission | ||||
|         if isinstance(self.permission, str): | ||||
|             try: | ||||
|                 app_label, codename = self.permission.split(".") | ||||
|             except ValueError as e: | ||||
|                 raise ValueError( | ||||
|                     "Permission name should be in the form " | ||||
|                     "app_label.permission_codename." | ||||
|                 ) from e | ||||
|             return get_object_or_404( | ||||
|                 Permission, codename=codename, content_type__app_label=app_label | ||||
|             ) | ||||
|         raise TypeError( | ||||
|             f"{self.__class__.__name__}.permission " | ||||
|             f"must be a string or a permission instance." | ||||
|         ) | ||||
|  | ||||
|     def get_success_url(self): | ||||
|         # if children classes define a success url, return it, | ||||
|         # else stay on the same page | ||||
|         return self.success_url or self.request.path | ||||
|   | ||||
| @@ -228,3 +228,38 @@ Les groupes de ban existants sont les suivants : | ||||
| - `Banned from buying alcohol` : les utilisateurs interdits de vente d'alcool (non mineurs) | ||||
| - `Banned from counters` : les utilisateurs interdits d'utilisation des comptoirs | ||||
| - `Banned to subscribe` : les utilisateurs interdits de cotisation | ||||
|  | ||||
| ## Groupes liés à une permission | ||||
|  | ||||
| Certaines actions sur le site demandent une permission en particulier, | ||||
| que l'on veut donner ou retirer n'importe quand. | ||||
|  | ||||
| Prenons par exemple les cotisations : lors de l'intégration, | ||||
| on veut permettre aux membres du bureau de l'Integ | ||||
| de créer des cotisations, et pareil pour les membres du bureau  | ||||
| de la Welcome Week pendant cette dernière. | ||||
|  | ||||
| Dans ces cas-là, il est pertinent de mettre à disposition | ||||
| des administrateurs du site une page leur permettant | ||||
| de gérer quels groupes ont une permission donnée. | ||||
| Pour ce faire, il existe  | ||||
| [PermissionGroupsUpdateView][core.views.PermissionGroupsUpdateView]. | ||||
|  | ||||
| Pour l'utiliser, il suffit de créer une vue qui en hérite | ||||
| et de lui dire quelle est la permission dont on veut gérer | ||||
| les groupes : | ||||
|  | ||||
| ```python | ||||
| from core.views.group import PermissionGroupsUpdateView | ||||
|  | ||||
|  | ||||
| class SubscriptionPermissionView(PermissionGroupsUpdateView): | ||||
|     permission = "subscription.add_subscription" | ||||
| ``` | ||||
|  | ||||
| Configurez l'url de la vue, et c'est tout ! | ||||
| La page ainsi générée contiendra un formulaire | ||||
| avec un unique champ permettant de sélectionner des groupes. | ||||
| Par défaut, seuls les utilisateurs avec la permission | ||||
| `auth.change_permission` auront accès à ce formulaire | ||||
| (donc, normalement, uniquement les utilisateurs Root). | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2025-01-19 18:12+0100\n" | ||||
| "POT-Creation-Date: 2025-02-12 15:55+0100\n" | ||||
| "PO-Revision-Date: 2016-07-18\n" | ||||
| "Last-Translator: Maréchal <thomas.girod@utbm.fr\n" | ||||
| "Language-Team: AE info <ae.info@utbm.fr>\n" | ||||
| @@ -2383,11 +2383,10 @@ msgstr "Confirmation" | ||||
| msgid "Cancel" | ||||
| msgstr "Annuler" | ||||
|  | ||||
| #: core/templates/core/edit.jinja core/templates/core/file_edit.jinja | ||||
| #: counter/templates/counter/cash_register_summary.jinja | ||||
| #: core/templates/core/edit.jinja | ||||
| #, python-format | ||||
| msgid "Edit %(obj)s" | ||||
| msgstr "Éditer %(obj)s" | ||||
| msgid "Edit %(name)s" | ||||
| msgstr "Éditer %(name)s" | ||||
|  | ||||
| #: core/templates/core/file.jinja core/templates/core/file_list.jinja | ||||
| msgid "File list" | ||||
| @@ -2457,6 +2456,12 @@ msgstr "octets" | ||||
| msgid "Download" | ||||
| msgstr "Télécharger" | ||||
|  | ||||
| #: core/templates/core/file_edit.jinja | ||||
| #: counter/templates/counter/cash_register_summary.jinja | ||||
| #, python-format | ||||
| msgid "Edit %(obj)s" | ||||
| msgstr "Éditer %(obj)s" | ||||
|  | ||||
| #: core/templates/core/file_list.jinja | ||||
| msgid "There is no file in this website." | ||||
| msgstr "Il n'y a pas de fichier sur ce site web." | ||||
| @@ -2914,7 +2919,7 @@ msgstr "Blouse" | ||||
| msgid "Not subscribed" | ||||
| msgstr "Non cotisant" | ||||
|  | ||||
| #: core/templates/core/user_detail.jinja | ||||
| #: core/templates/core/user_detail.jinja core/templates/core/user_tools.jinja | ||||
| #: subscription/templates/subscription/subscription.jinja | ||||
| msgid "New subscription" | ||||
| msgstr "Nouvelle cotisation" | ||||
| @@ -3146,15 +3151,6 @@ msgstr "Supprimer les messages forum d'un utilisateur" | ||||
| msgid "Bans" | ||||
| msgstr "Bans" | ||||
|  | ||||
| #: core/templates/core/user_tools.jinja | ||||
| msgid "Subscriptions" | ||||
| msgstr "Cotisations" | ||||
|  | ||||
| #: core/templates/core/user_tools.jinja | ||||
| #: subscription/templates/subscription/stats.jinja | ||||
| msgid "Subscription stats" | ||||
| msgstr "Statistiques de cotisation" | ||||
|  | ||||
| #: core/templates/core/user_tools.jinja counter/forms.py | ||||
| #: counter/views/mixins.py | ||||
| msgid "Counters" | ||||
| @@ -3227,6 +3223,19 @@ msgstr "Modérer les fichiers" | ||||
| msgid "Moderate pictures" | ||||
| msgstr "Modérer les photos" | ||||
|  | ||||
| #: core/templates/core/user_tools.jinja | ||||
| msgid "Subscriptions" | ||||
| msgstr "Cotisations" | ||||
|  | ||||
| #: core/templates/core/user_tools.jinja | ||||
| msgid "Manage permissions" | ||||
| msgstr "Gérer les permissions" | ||||
|  | ||||
| #: core/templates/core/user_tools.jinja | ||||
| #: subscription/templates/subscription/stats.jinja | ||||
| msgid "Subscription stats" | ||||
| msgstr "Statistiques de cotisation" | ||||
|  | ||||
| #: core/templates/core/user_tools.jinja pedagogy/templates/pedagogy/guide.jinja | ||||
| msgid "Create UV" | ||||
| msgstr "Créer UV" | ||||
| @@ -3355,6 +3364,10 @@ msgstr "Utilisateurs à ajouter au groupe" | ||||
| msgid "Users to remove from group" | ||||
| msgstr "Utilisateurs à retirer du groupe" | ||||
|  | ||||
| #: core/views/group.py | ||||
| msgid "Groups have been successfully updated." | ||||
| msgstr "Les groupes ont été mis à jour avec succès." | ||||
|  | ||||
| #: core/views/user.py | ||||
| msgid "We couldn't verify that this email actually exists" | ||||
| msgstr "Nous n'avons pas réussi à vérifier que cette adresse mail existe." | ||||
| @@ -5676,6 +5689,10 @@ msgstr "Cotisations par type" | ||||
| msgid "Existing member" | ||||
| msgstr "Membre existant" | ||||
|  | ||||
| #: subscription/views.py | ||||
| msgid "the groups that can create subscriptions" | ||||
| msgstr "les groupes pouvant créer des cotisations" | ||||
|  | ||||
| #: trombi/models.py | ||||
| msgid "subscription deadline" | ||||
| msgstr "fin des inscriptions" | ||||
|   | ||||
| @@ -517,14 +517,6 @@ SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = 1 | ||||
| SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = 2 | ||||
| SITH_PRODUCTTYPE_SUBSCRIPTION = 2 | ||||
|  | ||||
| # Defines which club lets its member the ability to make subscriptions | ||||
| # Elements of this list are club's id | ||||
| SITH_CAN_CREATE_SUBSCRIPTIONS = [1] | ||||
|  | ||||
| # Defines which clubs lets its members the ability to see users subscription history | ||||
| # Elements of this list are club's id | ||||
| SITH_CAN_READ_SUBSCRIPTION_HISTORY = [] | ||||
|  | ||||
| # Number of weeks before the end of a subscription when the subscriber can resubscribe | ||||
| SITH_SUBSCRIPTION_END = 10 | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ from typing import Callable | ||||
|  | ||||
| import pytest | ||||
| from dateutil.relativedelta import relativedelta | ||||
| from django.contrib.auth.models import Permission | ||||
| from django.test import Client | ||||
| from django.urls import reverse | ||||
| from django.utils.timezone import localdate | ||||
| @@ -108,7 +109,12 @@ def test_page_access( | ||||
|  | ||||
| @pytest.mark.django_db | ||||
| def test_submit_form_existing_user(client: Client, settings: SettingsWrapper): | ||||
|     client.force_login(board_user.make()) | ||||
|     client.force_login( | ||||
|         baker.make( | ||||
|             User, | ||||
|             user_permissions=Permission.objects.filter(codename="add_subscription"), | ||||
|         ) | ||||
|     ) | ||||
|     user = old_subscriber_user.make() | ||||
|     response = client.post( | ||||
|         reverse("subscription:fragment-existing-user"), | ||||
| @@ -133,7 +139,12 @@ def test_submit_form_existing_user(client: Client, settings: SettingsWrapper): | ||||
|  | ||||
| @pytest.mark.django_db | ||||
| def test_submit_form_new_user(client: Client, settings: SettingsWrapper): | ||||
|     client.force_login(board_user.make()) | ||||
|     client.force_login( | ||||
|         baker.make( | ||||
|             User, | ||||
|             user_permissions=Permission.objects.filter(codename="add_subscription"), | ||||
|         ) | ||||
|     ) | ||||
|     response = client.post( | ||||
|         reverse("subscription:fragment-new-user"), | ||||
|         { | ||||
|   | ||||
							
								
								
									
										43
									
								
								subscription/tests/test_permissions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								subscription/tests/test_permissions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| from django.contrib.auth.models import Permission | ||||
| from django.test import TestCase | ||||
| from django.urls import reverse | ||||
| from model_bakery import baker | ||||
| from pytest_django.asserts import assertRedirects | ||||
|  | ||||
| from club.models import Club, Membership | ||||
| from core.baker_recipes import subscriber_user | ||||
| from core.models import User | ||||
|  | ||||
|  | ||||
| class TestSubscriptionPermission(TestCase): | ||||
|     @classmethod | ||||
|     def setUpTestData(cls): | ||||
|         cls.user: User = subscriber_user.make() | ||||
|         cls.admin = baker.make(User, is_superuser=True) | ||||
|         cls.club = baker.make(Club) | ||||
|         baker.make(Membership, user=cls.user, club=cls.club, role=7) | ||||
|  | ||||
|     def test_give_permission(self): | ||||
|         self.client.force_login(self.admin) | ||||
|         response = self.client.post( | ||||
|             reverse("subscription:perms"), {"groups": [self.club.board_group_id]} | ||||
|         ) | ||||
|         assertRedirects(response, reverse("subscription:perms")) | ||||
|         assert self.user.has_perm("subscription.add_subscription") | ||||
|  | ||||
|     def test_remove_permission(self): | ||||
|         self.client.force_login(self.admin) | ||||
|         response = self.client.post(reverse("subscription:perms"), {"groups": []}) | ||||
|         assertRedirects(response, reverse("subscription:perms")) | ||||
|         assert not self.user.has_perm("subscription.add_subscription") | ||||
|  | ||||
|     def test_subscription_page_access(self): | ||||
|         self.client.force_login(self.user) | ||||
|         response = self.client.get(reverse("subscription:subscription")) | ||||
|         assert response.status_code == 403 | ||||
|  | ||||
|         self.club.board_group.permissions.add( | ||||
|             Permission.objects.get(codename="add_subscription") | ||||
|         ) | ||||
|         response = self.client.get(reverse("subscription:subscription")) | ||||
|         assert response.status_code == 200 | ||||
| @@ -20,6 +20,7 @@ from subscription.views import ( | ||||
|     CreateSubscriptionNewUserFragment, | ||||
|     NewSubscription, | ||||
|     SubscriptionCreatedFragment, | ||||
|     SubscriptionPermissionView, | ||||
|     SubscriptionsStatsView, | ||||
| ) | ||||
|  | ||||
| @@ -41,5 +42,10 @@ urlpatterns = [ | ||||
|         SubscriptionCreatedFragment.as_view(), | ||||
|         name="creation-success", | ||||
|     ), | ||||
|     path( | ||||
|         "perms/", | ||||
|         SubscriptionPermissionView.as_view(), | ||||
|         name="perms", | ||||
|     ), | ||||
|     path("stats/", SubscriptionsStatsView.as_view(), name="stats"), | ||||
| ] | ||||
|   | ||||
| @@ -14,13 +14,15 @@ | ||||
| # | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.contrib.auth.mixins import UserPassesTestMixin | ||||
| from django.contrib.auth.mixins import PermissionRequiredMixin | ||||
| from django.core.exceptions import PermissionDenied | ||||
| from django.urls import reverse, reverse_lazy | ||||
| from django.utils.timezone import localdate | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.views.generic import CreateView, DetailView, TemplateView | ||||
| from django.views.generic.edit import FormView | ||||
|  | ||||
| from core.views.group import PermissionGroupsUpdateView | ||||
| from counter.apps import PAYMENT_METHOD | ||||
| from subscription.forms import ( | ||||
|     SelectionDateForm, | ||||
| @@ -30,13 +32,9 @@ from subscription.forms import ( | ||||
| from subscription.models import Subscription | ||||
|  | ||||
|  | ||||
| class CanCreateSubscriptionMixin(UserPassesTestMixin): | ||||
|     def test_func(self): | ||||
|         return self.request.user.can_create_subscription | ||||
|  | ||||
|  | ||||
| class NewSubscription(CanCreateSubscriptionMixin, TemplateView): | ||||
| class NewSubscription(PermissionRequiredMixin, TemplateView): | ||||
|     template_name = "subscription/subscription.jinja" | ||||
|     permission_required = "subscription.add_subscription" | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         return super().get_context_data(**kwargs) | { | ||||
| @@ -49,8 +47,9 @@ class NewSubscription(CanCreateSubscriptionMixin, TemplateView): | ||||
|         } | ||||
|  | ||||
|  | ||||
| class CreateSubscriptionFragment(CanCreateSubscriptionMixin, CreateView): | ||||
| class CreateSubscriptionFragment(PermissionRequiredMixin, CreateView): | ||||
|     template_name = "subscription/fragments/creation_form.jinja" | ||||
|     permission_required = "subscription.add_subscription" | ||||
|  | ||||
|     def get_success_url(self): | ||||
|         return reverse( | ||||
| @@ -72,13 +71,21 @@ class CreateSubscriptionNewUserFragment(CreateSubscriptionFragment): | ||||
|     extra_context = {"post_url": reverse_lazy("subscription:fragment-new-user")} | ||||
|  | ||||
|  | ||||
| class SubscriptionCreatedFragment(CanCreateSubscriptionMixin, DetailView): | ||||
| class SubscriptionCreatedFragment(PermissionRequiredMixin, DetailView): | ||||
|     template_name = "subscription/fragments/creation_success.jinja" | ||||
|     permission_required = "subscription.add_subscription" | ||||
|     model = Subscription | ||||
|     pk_url_kwarg = "subscription_id" | ||||
|     context_object_name = "subscription" | ||||
|  | ||||
|  | ||||
| class SubscriptionPermissionView(PermissionGroupsUpdateView): | ||||
|     """Manage the groups that have access to the subscription creation page.""" | ||||
|  | ||||
|     permission = "subscription.add_subscription" | ||||
|     extra_context = {"object_name": _("the groups that can create subscriptions")} | ||||
|  | ||||
|  | ||||
| class SubscriptionsStatsView(FormView): | ||||
|     template_name = "subscription/stats.jinja" | ||||
|     form_class = SelectionDateForm | ||||
|   | ||||
		Reference in New Issue
	
	Block a user