From ec2b4afd4113b5a1d45c9c984f2f9617e7d46c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Pi=C3=A9tri?= Date: Wed, 10 Feb 2021 11:06:09 +0100 Subject: [PATCH] style: :art: Apply black and isort --- api/apps.py | 2 +- api/authentication.py | 6 +- api/permissions.py | 3 +- api/serializers.py | 5 - api/urls.py | 5 +- api/views.py | 5 +- cotisations/admin.py | 4 +- cotisations/api/serializers.py | 45 ++-- cotisations/api/urls.py | 5 +- cotisations/api/views.py | 32 +-- cotisations/apps.py | 2 +- cotisations/forms.py | 19 +- cotisations/models.py | 70 ++++-- cotisations/payment_methods/__init__.py | 2 +- cotisations/payment_methods/balance/models.py | 3 +- cotisations/payment_methods/cheque/forms.py | 2 +- cotisations/payment_methods/cheque/urls.py | 1 + cotisations/payment_methods/cheque/views.py | 8 +- .../payment_methods/comnpay/comnpay.py | 8 +- cotisations/payment_methods/comnpay/models.py | 8 +- cotisations/payment_methods/comnpay/urls.py | 1 + cotisations/payment_methods/comnpay/views.py | 16 +- cotisations/payment_methods/forms.py | 3 +- cotisations/payment_methods/free/models.py | 6 +- .../payment_methods/note_kfet/models.py | 6 +- cotisations/payment_methods/note_kfet/note.py | 2 +- cotisations/payment_methods/note_kfet/urls.py | 1 + .../payment_methods/note_kfet/views.py | 17 +- cotisations/payment_methods/urls.py | 9 +- cotisations/test_models.py | 48 ++-- cotisations/test_views.py | 11 +- cotisations/tex.py | 13 +- cotisations/urls.py | 23 +- cotisations/utils.py | 18 +- cotisations/views.py | 93 +++----- cotisations/views_autocomplete.py | 13 +- freeradius_utils/auth.py | 26 +- ldap_sync/admin.py | 10 +- ldap_sync/apps.py | 2 +- ldap_sync/management/commands/ldap_rebuild.py | 7 +- ldap_sync/management/commands/ldap_sync.py | 2 +- ldap_sync/models.py | 43 ++-- ldap_sync/urls.py | 3 +- logs/apps.py | 2 +- logs/forms.py | 9 +- logs/models.py | 87 +++---- logs/urls.py | 6 +- logs/views.py | 98 +++----- machines/admin.py | 22 +- machines/api/serializers.py | 78 +++--- machines/api/urls.py | 6 +- machines/api/views.py | 75 +++--- machines/apps.py | 2 +- machines/forms.py | 42 +--- machines/models.py | 164 ++++++------- machines/urls.py | 65 +++-- machines/views.py | 154 +++++------- machines/views_autocomplete.py | 17 +- multi_op/forms.py | 10 +- multi_op/models.py | 15 +- multi_op/preferences/__init__.py | 2 +- multi_op/preferences/forms.py | 3 +- multi_op/preferences/models.py | 2 +- multi_op/preferences/views.py | 14 +- multi_op/views.py | 46 ++-- preferences/admin.py | 19 +- preferences/api/serializers.py | 33 +-- preferences/api/urls.py | 8 +- preferences/api/views.py | 34 +-- preferences/apps.py | 2 +- preferences/forms.py | 59 ++--- preferences/models.py | 27 +-- preferences/urls.py | 12 +- preferences/views.py | 64 ++--- re2o/acl.py | 2 +- re2o/aes_field.py | 14 +- re2o/apps.py | 2 +- re2o/base.py | 13 +- re2o/context_processors.py | 19 +- re2o/contributors.py | 82 +++---- re2o/field_permissions.py | 4 +- re2o/login.py | 22 +- re2o/mail_utils.py | 16 +- re2o/management/commands/gen_contrib.py | 1 + re2o/mixins.py | 3 +- re2o/script_utils.py | 26 +- re2o/settings.py | 24 +- re2o/settings_default.py | 24 +- re2o/settings_local.example.py | 24 +- re2o/templatetags/acl.py | 5 +- re2o/templatetags/pagination_extra.py | 10 +- re2o/templatetags/self_adhesion.py | 2 +- re2o/urls.py | 5 +- re2o/utils.py | 93 +++++--- re2o/views.py | 35 ++- re2o/widgets.py | 8 +- re2o/wsgi.py | 3 +- search/apps.py | 2 +- search/engine.py | 13 +- search/forms.py | 1 + search/views.py | 31 +-- test_utils/runner.py | 7 +- tickets/admin.py | 7 +- tickets/forms.py | 18 +- tickets/models.py | 51 ++-- tickets/preferences/__init__.py | 2 +- tickets/preferences/forms.py | 3 +- tickets/preferences/models.py | 2 +- tickets/preferences/views.py | 13 +- tickets/urls.py | 6 +- tickets/views.py | 63 +++-- topologie/admin.py | 16 +- topologie/api/serializers.py | 51 ++-- topologie/api/urls.py | 3 +- topologie/api/views.py | 47 ++-- topologie/apps.py | 2 +- topologie/forms.py | 28 +-- topologie/models.py | 53 ++--- topologie/urls.py | 53 +++-- topologie/views.py | 83 +++---- topologie/views_autocomplete.py | 15 +- users/admin.py | 55 +++-- users/api/serializers.py | 42 ++-- users/api/urls.py | 5 +- users/api/views.py | 51 ++-- users/apps.py | 2 +- users/forms.py | 103 ++++---- users/management/commands/anonymise.py | 23 +- users/management/commands/chgpass.py | 7 +- users/management/commands/chsh.py | 6 +- .../management/commands/clean_notyetactive.py | 12 +- .../management/commands/derniere_connexion.py | 2 +- .../commands/disable_emailnotyetconfirmed.py | 10 +- users/models.py | 225 +++++++++--------- users/signals.py | 12 +- users/test_models.py | 6 +- users/tests.py | 8 +- users/urls.py | 47 ++-- users/views.py | 190 ++++++++------- users/views_autocomplete.py | 10 +- users/widgets.py | 21 +- 141 files changed, 1687 insertions(+), 1942 deletions(-) diff --git a/api/apps.py b/api/apps.py index f2bfb744..f7cbb3b9 100644 --- a/api/apps.py +++ b/api/apps.py @@ -8,4 +8,4 @@ from django.apps import AppConfig class ApiConfig(AppConfig): """Configuration of api app.""" - name = "api" \ No newline at end of file + name = "api" diff --git a/api/authentication.py b/api/authentication.py index 1fa381e0..af9afa69 100644 --- a/api/authentication.py +++ b/api/authentication.py @@ -31,12 +31,10 @@ from rest_framework.authentication import TokenAuthentication class ExpiringTokenAuthentication(TokenAuthentication): - """Authenticate a user if the provided token is valid and not expired. - """ + """Authenticate a user if the provided token is valid and not expired.""" def authenticate_credentials(self, key): - """See base class. Add the verification the token is not expired. - """ + """See base class. Add the verification the token is not expired.""" base = super(ExpiringTokenAuthentication, self) user, token = base.authenticate_credentials(key) diff --git a/api/permissions.py b/api/permissions.py index 7ab96f1e..8e3bd2d4 100644 --- a/api/permissions.py +++ b/api/permissions.py @@ -22,8 +22,9 @@ """Defines the permission classes used in the API. """ -from rest_framework import permissions, exceptions from django.http import Http404 +from rest_framework import exceptions, permissions + from . import acl diff --git a/api/serializers.py b/api/serializers.py index de57e26a..cbbfafdb 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -24,7 +24,6 @@ from rest_framework import serializers - # The namespace used for the API. It must match the namespace used in the # urlpatterns to include the API URLs. API_NAMESPACE = "api" @@ -59,7 +58,3 @@ class NamespacedHMSerializer(serializers.HyperlinkedModelSerializer): serializer_related_field = NamespacedHRField serializer_url_field = NamespacedHIField - - - - diff --git a/api/urls.py b/api/urls.py index 8d57b172..be7b197c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -28,12 +28,13 @@ can also be register. That way a complete API root page presenting all URLs can be generated automatically. """ -from django.urls import path, include from importlib import import_module +from django.conf import settings +from django.urls import include, path + from . import views from .routers import AllViewsRouter -from django.conf import settings app_name = "api" diff --git a/api/views.py b/api/views.py index 70ff33bf..5a4a902f 100644 --- a/api/views.py +++ b/api/views.py @@ -29,9 +29,9 @@ the response (JSON or other), the CSRF exempting, ... import datetime from django.conf import settings -from django.db.models import Q from django.contrib.auth.models import Group -from rest_framework import viewsets, generics, views +from django.db.models import Q +from rest_framework import generics, views, viewsets from rest_framework.authtoken.models import Token from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.response import Response @@ -41,7 +41,6 @@ from .pagination import PageSizedPagination from .permissions import ACLPermission - class ObtainExpiringAuthToken(ObtainAuthToken): """Exposes a view to obtain a authentication token. diff --git a/cotisations/admin.py b/cotisations/admin.py index ea280fca..5ea24e32 100644 --- a/cotisations/admin.py +++ b/cotisations/admin.py @@ -29,8 +29,8 @@ from __future__ import unicode_literals from django.contrib import admin from reversion.admin import VersionAdmin -from .models import Facture, Article, Banque, Paiement, Cotisation, Vente -from .models import CustomInvoice, CostEstimate +from .models import (Article, Banque, CostEstimate, Cotisation, CustomInvoice, + Facture, Paiement, Vente) class FactureAdmin(VersionAdmin): diff --git a/cotisations/api/serializers.py b/cotisations/api/serializers.py index f7279ea9..bfc785c7 100644 --- a/cotisations/api/serializers.py +++ b/cotisations/api/serializers.py @@ -23,13 +23,13 @@ from rest_framework import serializers import cotisations.models as cotisations import preferences.models as preferences -from api.serializers import NamespacedHRField, NamespacedHIField, NamespacedHMSerializer +from api.serializers import (NamespacedHIField, NamespacedHMSerializer, + NamespacedHRField) from users.api.serializers import UserSerializer class FactureSerializer(NamespacedHMSerializer): - """Serialize `cotisations.models.Facture` objects. - """ + """Serialize `cotisations.models.Facture` objects.""" class Meta: model = cotisations.Facture @@ -54,8 +54,7 @@ class BaseInvoiceSerializer(NamespacedHMSerializer): class VenteSerializer(NamespacedHMSerializer): - """Serialize `cotisations.models.Vente` objects. - """ + """Serialize `cotisations.models.Vente` objects.""" class Meta: model = cotisations.Vente @@ -74,17 +73,24 @@ class VenteSerializer(NamespacedHMSerializer): class ArticleSerializer(NamespacedHMSerializer): - """Serialize `cotisations.models.Article` objects. - """ + """Serialize `cotisations.models.Article` objects.""" class Meta: model = cotisations.Article - fields = ("name", "prix", "duration_membership", "duration_days_membership", "duration_connection", "duration_days_connection", "type_user", "api_url") + fields = ( + "name", + "prix", + "duration_membership", + "duration_days_membership", + "duration_connection", + "duration_days_connection", + "type_user", + "api_url", + ) class BanqueSerializer(NamespacedHMSerializer): - """Serialize `cotisations.models.Banque` objects. - """ + """Serialize `cotisations.models.Banque` objects.""" class Meta: model = cotisations.Banque @@ -92,8 +98,7 @@ class BanqueSerializer(NamespacedHMSerializer): class PaiementSerializer(NamespacedHMSerializer): - """Serialize `cotisations.models.Paiement` objects. - """ + """Serialize `cotisations.models.Paiement` objects.""" class Meta: model = cotisations.Paiement @@ -101,17 +106,23 @@ class PaiementSerializer(NamespacedHMSerializer): class CotisationSerializer(NamespacedHMSerializer): - """Serialize `cotisations.models.Cotisation` objects. - """ + """Serialize `cotisations.models.Cotisation` objects.""" class Meta: model = cotisations.Cotisation - fields = ("vente", "type_cotisation", "date_start_con", "date_end_con", "date_start_memb", "date_end_memb", "api_url") + fields = ( + "vente", + "type_cotisation", + "date_start_con", + "date_end_con", + "date_start_memb", + "date_end_memb", + "api_url", + ) class ReminderUsersSerializer(UserSerializer): - """Serialize the data about a mailing member. - """ + """Serialize the data about a mailing member.""" class Meta(UserSerializer.Meta): fields = ("get_full_name", "get_mail") diff --git a/cotisations/api/urls.py b/cotisations/api/urls.py index 85e60c72..69af4105 100644 --- a/cotisations/api/urls.py +++ b/cotisations/api/urls.py @@ -27,12 +27,11 @@ urls_viewset = [ (r"cotisations/article", views.ArticleViewSet, None), (r"cotisations/banque", views.BanqueViewSet, None), (r"cotisations/paiement", views.PaiementViewSet, None), - (r"cotisations/cotisation", views.CotisationViewSet, None) + (r"cotisations/cotisation", views.CotisationViewSet, None), ] urls_view = [ (r"cotisations/reminder-get-users", views.ReminderView), - # Deprecated (r"reminder/get-users", views.ReminderView), -] \ No newline at end of file +] diff --git a/cotisations/api/views.py b/cotisations/api/views.py index 2995e721..bdc7504d 100644 --- a/cotisations/api/views.py +++ b/cotisations/api/views.py @@ -19,71 +19,65 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from rest_framework import viewsets, generics +from rest_framework import generics, viewsets -from . import serializers import cotisations.models as cotisations import preferences.models as preferences +from . import serializers + + class FactureViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `cotisations.models.Facture` objects. - """ + """Exposes list and details of `cotisations.models.Facture` objects.""" queryset = cotisations.Facture.objects.all() serializer_class = serializers.FactureSerializer class FactureViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `cotisations.models.Facture` objects. - """ + """Exposes list and details of `cotisations.models.Facture` objects.""" queryset = cotisations.BaseInvoice.objects.all() serializer_class = serializers.BaseInvoiceSerializer class VenteViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `cotisations.models.Vente` objects. - """ + """Exposes list and details of `cotisations.models.Vente` objects.""" queryset = cotisations.Vente.objects.all() serializer_class = serializers.VenteSerializer class ArticleViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `cotisations.models.Article` objects. - """ + """Exposes list and details of `cotisations.models.Article` objects.""" queryset = cotisations.Article.objects.all() serializer_class = serializers.ArticleSerializer class BanqueViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `cotisations.models.Banque` objects. - """ + """Exposes list and details of `cotisations.models.Banque` objects.""" queryset = cotisations.Banque.objects.all() serializer_class = serializers.BanqueSerializer class PaiementViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `cotisations.models.Paiement` objects. - """ + """Exposes list and details of `cotisations.models.Paiement` objects.""" queryset = cotisations.Paiement.objects.all() serializer_class = serializers.PaiementSerializer class CotisationViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `cotisations.models.Cotisation` objects. - """ + """Exposes list and details of `cotisations.models.Cotisation` objects.""" queryset = cotisations.Cotisation.objects.all() serializer_class = serializers.CotisationSerializer class ReminderView(generics.ListAPIView): - """Output for users to remind an end of their subscription. - """ + """Output for users to remind an end of their subscription.""" queryset = preferences.Reminder.objects.all() - serializer_class = serializers.ReminderSerializer \ No newline at end of file + serializer_class = serializers.ReminderSerializer diff --git a/cotisations/apps.py b/cotisations/apps.py index 1b85f0b3..16bf8250 100644 --- a/cotisations/apps.py +++ b/cotisations/apps.py @@ -8,4 +8,4 @@ from django.apps import AppConfig class CotisationsConfig(AppConfig): """Configuration of cotisations app.""" - name = "cotisations" \ No newline at end of file + name = "cotisations" diff --git a/cotisations/forms.py b/cotisations/forms.py index f9e44686..fcbe66f6 100644 --- a/cotisations/forms.py +++ b/cotisations/forms.py @@ -37,25 +37,18 @@ of each of the method. from __future__ import unicode_literals from django import forms -from django.db.models import Q -from django.forms import ModelForm, Form from django.core.validators import MinValueValidator - -from django.utils.translation import ugettext_lazy as _ +from django.db.models import Q +from django.forms import Form, ModelForm from django.shortcuts import get_object_or_404 +from django.utils.translation import ugettext_lazy as _ from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin from re2o.widgets import AutocompleteModelWidget -from .models import ( - Article, - Paiement, - Facture, - Banque, - CustomInvoice, - Vente, - CostEstimate, -) + +from .models import (Article, Banque, CostEstimate, CustomInvoice, Facture, + Paiement, Vente) from .payment_methods import balance diff --git a/cotisations/models.py b/cotisations/models.py index c93725a4..556e13f5 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -32,29 +32,29 @@ each. """ from __future__ import unicode_literals -from dateutil.relativedelta import relativedelta +from dateutil.relativedelta import relativedelta +from django.contrib import messages +from django.core.validators import MinValueValidator from django.db import models -from django.db.models import Q, Max -from django.db.models.signals import post_save, post_delete +from django.db.models import Max, Q +from django.db.models.signals import post_delete, post_save from django.dispatch import receiver from django.forms import ValidationError -from django.core.validators import MinValueValidator +from django.shortcuts import redirect +from django.urls import reverse from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from django.urls import reverse -from django.shortcuts import redirect -from django.contrib import messages -from preferences.models import CotisationsOption +import users.models +import users.signals +from cotisations.utils import (find_payment_method, send_mail_invoice, + send_mail_voucher) +from cotisations.validators import check_no_balance from machines.models import regen +from preferences.models import CotisationsOption from re2o.field_permissions import FieldPermissionModelMixin from re2o.mixins import AclMixin, RevMixin -import users.signals -import users.models - -from cotisations.utils import find_payment_method, send_mail_invoice, send_mail_voucher -from cotisations.validators import check_no_balance class BaseInvoice(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): @@ -360,7 +360,13 @@ def facture_post_save(**kwargs): if facture.valid: user = facture.user user.set_active() - users.signals.synchronise.send(sender=users.models.User, instance=user, base=False, access_refresh=True, mac_refresh=False) + users.signals.synchronise.send( + sender=users.models.User, + instance=user, + base=False, + access_refresh=True, + mac_refresh=False, + ) @receiver(post_delete, sender=Facture) @@ -369,7 +375,13 @@ def facture_post_delete(**kwargs): Synchronise the LDAP user after an invoice has been deleted. """ user = kwargs["instance"].user - users.signals.synchronise.send(sender=users.models.User, instance=user, base=False, access_refresh=True, mac_refresh=False) + users.signals.synchronise.send( + sender=users.models.User, + instance=user, + base=False, + access_refresh=True, + mac_refresh=False, + ) class CustomInvoice(BaseInvoice): @@ -481,9 +493,7 @@ class Vente(RevMixin, AclMixin, models.Model): ) class Meta: - permissions = ( - ("change_all_vente", _("Can edit all the previous purchases")), - ) + permissions = (("change_all_vente", _("Can edit all the previous purchases")),) verbose_name = _("purchase") verbose_name_plural = _("purchases") @@ -660,7 +670,13 @@ def vente_post_save(**kwargs): purchase.cotisation.save() user = purchase.facture.facture.user user.set_active() - users.signals.synchronise.send(sender=users.models.User, instance=user, base=True, access_refresh=True, mac_refresh=False) + users.signals.synchronise.send( + sender=users.models.User, + instance=user, + base=True, + access_refresh=True, + mac_refresh=False, + ) # TODO : change vente to purchase @@ -676,7 +692,13 @@ def vente_post_delete(**kwargs): return if purchase.type_cotisation: user = invoice.user - users.signals.synchronise.send(sender=users.models.User, instance=user, base=True, access_refresh=True, mac_refresh=False) + users.signals.synchronise.send( + sender=users.models.User, + instance=user, + base=True, + access_refresh=True, + mac_refresh=False, + ) class Article(RevMixin, AclMixin, models.Model): @@ -740,9 +762,7 @@ class Article(RevMixin, AclMixin, models.Model): unique_together = ("name", "type_user") class Meta: - permissions = ( - ("buy_every_article", _("Can buy every article")), - ) + permissions = (("buy_every_article", _("Can buy every article")),) verbose_name = "article" verbose_name_plural = "articles" @@ -844,9 +864,7 @@ class Paiement(RevMixin, AclMixin, models.Model): ) class Meta: - permissions = ( - ("use_every_payment", _("Can use every payment method")), - ) + permissions = (("use_every_payment", _("Can use every payment method")),) verbose_name = _("payment method") verbose_name_plural = _("payment methods") diff --git a/cotisations/payment_methods/__init__.py b/cotisations/payment_methods/__init__.py index 1170bd92..bd4bf525 100644 --- a/cotisations/payment_methods/__init__.py +++ b/cotisations/payment_methods/__init__.py @@ -127,6 +127,6 @@ method to your model, where `form` is an instance of """ -from . import comnpay, cheque, balance, note_kfet, free, urls +from . import balance, cheque, comnpay, free, note_kfet, urls PAYMENT_METHODS = [comnpay, cheque, balance, note_kfet, free] diff --git a/cotisations/payment_methods/balance/models.py b/cotisations/payment_methods/balance/models.py index 9f07f930..206e3cf8 100644 --- a/cotisations/payment_methods/balance/models.py +++ b/cotisations/payment_methods/balance/models.py @@ -18,12 +18,11 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from django.contrib import messages from django.db import models from django.shortcuts import redirect from django.urls import reverse from django.utils.translation import ugettext_lazy as _ -from django.contrib import messages - from cotisations.models import Paiement from cotisations.payment_methods.mixins import PaymentMethodMixin diff --git a/cotisations/payment_methods/cheque/forms.py b/cotisations/payment_methods/cheque/forms.py index f83cc8b3..d367771a 100644 --- a/cotisations/payment_methods/cheque/forms.py +++ b/cotisations/payment_methods/cheque/forms.py @@ -20,8 +20,8 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from django import forms -from re2o.mixins import FormRevMixin from cotisations.models import Facture as Invoice +from re2o.mixins import FormRevMixin class InvoiceForm(FormRevMixin, forms.ModelForm): diff --git a/cotisations/payment_methods/cheque/urls.py b/cotisations/payment_methods/cheque/urls.py index 0187ae53..72a2f913 100644 --- a/cotisations/payment_methods/cheque/urls.py +++ b/cotisations/payment_methods/cheque/urls.py @@ -19,6 +19,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from django.conf.urls import url + from . import views urlpatterns = [url(r"^validate/(?P[0-9]+)$", views.cheque, name="validate")] diff --git a/cotisations/payment_methods/cheque/views.py b/cotisations/payment_methods/cheque/views.py index 191e4159..c2df1165 100644 --- a/cotisations/payment_methods/cheque/views.py +++ b/cotisations/payment_methods/cheque/views.py @@ -23,17 +23,17 @@ Here are defined some views dedicated to cheque payement. """ -from django.urls import reverse -from django.shortcuts import redirect, render, get_object_or_404 -from django.contrib.auth.decorators import login_required from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.shortcuts import get_object_or_404, redirect, render +from django.urls import reverse from django.utils.translation import ugettext as _ from cotisations.models import Facture as Invoice from cotisations.utils import find_payment_method -from .models import ChequePayment from .forms import InvoiceForm +from .models import ChequePayment @login_required diff --git a/cotisations/payment_methods/comnpay/comnpay.py b/cotisations/payment_methods/comnpay/comnpay.py index b03239a4..993eed7e 100644 --- a/cotisations/payment_methods/comnpay/comnpay.py +++ b/cotisations/payment_methods/comnpay/comnpay.py @@ -3,15 +3,15 @@ The module in charge of handling the negociation with Comnpay for online payment """ -import time -from random import randrange import base64 import hashlib +import time from collections import OrderedDict +from random import randrange class Transaction: - """ The class representing a transaction with all the functions + """The class representing a transaction with all the functions used during the negociation """ @@ -35,7 +35,7 @@ class Transaction: self.idTransaction = "" def buildSecretHTML(self, produit="Produit", montant="0.00", idTransaction=""): - """ Build an HTML hidden form with the different parameters for the + """Build an HTML hidden form with the different parameters for the transaction """ if idTransaction == "": diff --git a/cotisations/payment_methods/comnpay/models.py b/cotisations/payment_methods/comnpay/models.py index ef0c6cf5..8aa52bbb 100644 --- a/cotisations/payment_methods/comnpay/models.py +++ b/cotisations/payment_methods/comnpay/models.py @@ -25,8 +25,8 @@ from django.utils.translation import ugettext_lazy as _ from cotisations.models import Paiement from cotisations.payment_methods.mixins import PaymentMethodMixin - from re2o.aes_field import AESEncryptedField + from .comnpay import Transaction @@ -53,8 +53,7 @@ class ComnpayPayment(PaymentMethodMixin, models.Model): minimum_payment = models.DecimalField( verbose_name=_("minimum payment"), help_text=_( - "The minimal amount of money you have to use when paying with" - " ComNpay." + "The minimal amount of money you have to use when paying with" " ComNpay." ), max_digits=5, decimal_places=2, @@ -107,8 +106,7 @@ class ComnpayPayment(PaymentMethodMixin, models.Model): return render(request, "cotisations/payment.html", r) def check_price(self, price, *args, **kwargs): - """Checks that the price meets the requirement to be paid with ComNpay. - """ + """Checks that the price meets the requirement to be paid with ComNpay.""" return ( (price >= self.minimum_payment), _( diff --git a/cotisations/payment_methods/comnpay/urls.py b/cotisations/payment_methods/comnpay/urls.py index babf2448..a90ca0fd 100644 --- a/cotisations/payment_methods/comnpay/urls.py +++ b/cotisations/payment_methods/comnpay/urls.py @@ -19,6 +19,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from django.conf.urls import url + from . import views urlpatterns = [ diff --git a/cotisations/payment_methods/comnpay/views.py b/cotisations/payment_methods/comnpay/views.py index 38bcbf96..e7055b6b 100644 --- a/cotisations/payment_methods/comnpay/views.py +++ b/cotisations/payment_methods/comnpay/views.py @@ -25,16 +25,17 @@ Here are the views needed by comnpay from collections import OrderedDict -from django.urls import reverse -from django.shortcuts import redirect, get_object_or_404 -from django.contrib.auth.decorators import login_required from django.contrib import messages -from django.views.decorators.csrf import csrf_exempt +from django.contrib.auth.decorators import login_required +from django.http import HttpResponse, HttpResponseBadRequest +from django.shortcuts import get_object_or_404, redirect +from django.urls import reverse from django.utils.datastructures import MultiValueDictKeyError from django.utils.translation import ugettext as _ -from django.http import HttpResponse, HttpResponseBadRequest +from django.views.decorators.csrf import csrf_exempt from cotisations.models import Facture + from .comnpay import Transaction from .models import ComnpayPayment @@ -55,7 +56,10 @@ def accept_payment(request, factureid): ) # In case a cotisation was bought, inform the user, the # cotisation time has been extended too - if any(purchase.test_membership_or_connection() for purchase in invoice.vente_set.all()): + if any( + purchase.test_membership_or_connection() + for purchase in invoice.vente_set.all() + ): messages.success( request, _( diff --git a/cotisations/payment_methods/forms.py b/cotisations/payment_methods/forms.py index 447fa38f..3b2b1f34 100644 --- a/cotisations/payment_methods/forms.py +++ b/cotisations/payment_methods/forms.py @@ -21,9 +21,10 @@ from django import forms from django.utils.translation import ugettext_lazy as _ -from . import PAYMENT_METHODS from cotisations.utils import find_payment_method +from . import PAYMENT_METHODS + def payment_method_factory(payment, *args, creation=True, **kwargs): """This function finds the right payment method form for a given payment. diff --git a/cotisations/payment_methods/free/models.py b/cotisations/payment_methods/free/models.py index a4c24459..bcecab35 100644 --- a/cotisations/payment_methods/free/models.py +++ b/cotisations/payment_methods/free/models.py @@ -18,10 +18,9 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from django.contrib import messages from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.contrib import messages - from cotisations.models import Paiement from cotisations.payment_methods.mixins import PaymentMethodMixin @@ -43,8 +42,7 @@ class FreePayment(PaymentMethodMixin, models.Model): ) def end_payment(self, invoice, request): - """Ends the payment normally. - """ + """Ends the payment normally.""" return invoice.paiement.end_payment(invoice, request, use_payment_method=False) def check_price(self, price, user, *args, **kwargs): diff --git a/cotisations/payment_methods/note_kfet/models.py b/cotisations/payment_methods/note_kfet/models.py index 4f1a8152..a71b79f8 100644 --- a/cotisations/payment_methods/note_kfet/models.py +++ b/cotisations/payment_methods/note_kfet/models.py @@ -19,17 +19,15 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +from django.contrib import messages from django.db import models -from django.shortcuts import render +from django.shortcuts import redirect, render from django.urls import reverse from django.utils.translation import ugettext_lazy as _ -from django.contrib import messages from cotisations.models import Paiement from cotisations.payment_methods.mixins import PaymentMethodMixin -from django.shortcuts import render, redirect - class NotePayment(PaymentMethodMixin, models.Model): """ diff --git a/cotisations/payment_methods/note_kfet/note.py b/cotisations/payment_methods/note_kfet/note.py index 40811913..8bac3158 100755 --- a/cotisations/payment_methods/note_kfet/note.py +++ b/cotisations/payment_methods/note_kfet/note.py @@ -5,8 +5,8 @@ """ Module pour dialoguer avec la NoteKfet2015 """ -import socket import json +import socket import ssl import traceback diff --git a/cotisations/payment_methods/note_kfet/urls.py b/cotisations/payment_methods/note_kfet/urls.py index a7fe3046..3fb20df8 100644 --- a/cotisations/payment_methods/note_kfet/urls.py +++ b/cotisations/payment_methods/note_kfet/urls.py @@ -19,6 +19,7 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from django.conf.urls import url + from . import views urlpatterns = [ diff --git a/cotisations/payment_methods/note_kfet/views.py b/cotisations/payment_methods/note_kfet/views.py index a3fb54b3..c0af4c8b 100644 --- a/cotisations/payment_methods/note_kfet/views.py +++ b/cotisations/payment_methods/note_kfet/views.py @@ -26,22 +26,23 @@ Here are the views needed by comnpay from collections import OrderedDict -from django.urls import reverse -from django.shortcuts import redirect, get_object_or_404 -from django.contrib.auth.decorators import login_required from django.contrib import messages -from django.views.decorators.csrf import csrf_exempt +from django.contrib.auth.decorators import login_required +from django.http import HttpResponse, HttpResponseBadRequest +from django.shortcuts import get_object_or_404, redirect +from django.urls import reverse from django.utils.datastructures import MultiValueDictKeyError from django.utils.translation import ugettext as _ -from django.http import HttpResponse, HttpResponseBadRequest +from django.views.decorators.csrf import csrf_exempt from cotisations.models import Facture from cotisations.utils import find_payment_method -from .models import NotePayment -from re2o.views import form from re2o.acl import can_create, can_edit -from .note import login, don +from re2o.views import form + from .forms import NoteCredentialForm +from .models import NotePayment +from .note import don, login @login_required diff --git a/cotisations/payment_methods/urls.py b/cotisations/payment_methods/urls.py index c413fac1..6ebcb6fa 100644 --- a/cotisations/payment_methods/urls.py +++ b/cotisations/payment_methods/urls.py @@ -19,10 +19,11 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from django.conf.urls import include, url -from . import comnpay, cheque, note_kfet + +from . import cheque, comnpay, note_kfet urlpatterns = [ - url(r"^comnpay/", include((comnpay.urls, 'comnpay'), namespace="comnpay")), - url(r"^cheque/", include((cheque.urls, 'cheque'), namespace="cheque")), - url(r"^note_kfet/", include((note_kfet.urls, 'note_kfet'), namespace="note_kfet")), + url(r"^comnpay/", include((comnpay.urls, "comnpay"), namespace="comnpay")), + url(r"^cheque/", include((cheque.urls, "cheque"), namespace="cheque")), + url(r"^note_kfet/", include((note_kfet.urls, "note_kfet"), namespace="note_kfet")), ] diff --git a/cotisations/test_models.py b/cotisations/test_models.py index 08c4a3b8..ea19725b 100644 --- a/cotisations/test_models.py +++ b/cotisations/test_models.py @@ -1,11 +1,12 @@ -from django.test import TestCase - import datetime -from django.utils import timezone + from dateutil.relativedelta import relativedelta +from django.test import TestCase +from django.utils import timezone from users.models import User -from .models import Vente, Facture, Cotisation, Paiement + +from .models import Cotisation, Facture, Paiement, Vente class VenteModelTests(TestCase): @@ -74,7 +75,7 @@ class VenteModelTests(TestCase): def test_one_month_and_one_week_cotisation(self): """ It should be possible to have one day membership. - Add one mounth and one week of membership and one mounth + Add one mounth and one week of membership and one mounth and one week of connection """ date = timezone.now() @@ -111,12 +112,21 @@ class VenteModelTests(TestCase): duration_days_connection=1, duration_membership=0, duration_deys_membership=1, - prix=0 + prix=0, + ) + v.create_cotis( + date_start_con=timezone.make_aware(datetime.datetime(1998, 10, 16)), + date_start_memb=timezone.make_aware(datetime.datetime(1998, 10, 16)), ) - v.create_cotis(date_start_con=timezone.make_aware(datetime.datetime(1998, 10, 16)), date_start_memb=timezone.make_aware(datetime.datetime(1998, 10, 16))) v.save() - self.assertEqual(v.cotisation.date_end_con, timezone.make_aware(datetime.datetime(1998, 10, 17))) - self.assertEqual(v.cotisation.date_end_memb, timezone.make_aware(datetime.datetime(1998, 10, 17))) + self.assertEqual( + v.cotisation.date_end_con, + timezone.make_aware(datetime.datetime(1998, 10, 17)), + ) + self.assertEqual( + v.cotisation.date_end_memb, + timezone.make_aware(datetime.datetime(1998, 10, 17)), + ) def test_one_day_cotisation_membership_only(self): """ @@ -207,12 +217,21 @@ class VenteModelTests(TestCase): duration_days_connection=0, duration_membership=0, duration_days_membership=1, - prix=0 + prix=0, + ) + v.create_cotis( + date_start_con=timezone.make_aware(datetime.datetime(1998, 10, 16)), + date_start_memb=timezone.make_aware(datetime.datetime(1998, 10, 16)), ) - v.create_cotis(date_start_con=timezone.make_aware(datetime.datetime(1998, 10, 16)), date_start_memb=timezone.make_aware(datetime.datetime(1998, 10, 16))) v.save() - self.assertEqual(v.cotisation.date_end_con, timezone.make_aware(datetime.datetime(1998, 10, 17))) - self.assertEqual(v.cotisation.date_end_memb, timezone.make_aware(datetime.datetime(1998, 10, 16))) + self.assertEqual( + v.cotisation.date_end_con, + timezone.make_aware(datetime.datetime(1998, 10, 17)), + ) + self.assertEqual( + v.cotisation.date_end_memb, + timezone.make_aware(datetime.datetime(1998, 10, 16)), + ) def test_cotisation_membership_diff_connection(self): """ @@ -252,9 +271,11 @@ class FactureModelTests(TestCase): def setUp(self): self.user = User.objects.create(pseudo="testUserPlop", email="test@example.org") self.paiement = Paiement.objects.create(moyen="test payment") + def tearDown(self): self.user.delete() self.paiement.delete() + def test_cotisations_prolongation(self): """When user already have one valid cotisation, the new one should be added at the end of the existing one.""" @@ -300,4 +321,3 @@ class FactureModelTests(TestCase): raise e invoice1.delete() invoice2.delete() - diff --git a/cotisations/test_views.py b/cotisations/test_views.py index c15848cb..9f9b80c7 100644 --- a/cotisations/test_views.py +++ b/cotisations/test_views.py @@ -1,13 +1,14 @@ +import datetime + +from dateutil.relativedelta import relativedelta +from django.contrib.auth.models import Permission from django.test import TestCase from django.urls import reverse -from django.contrib.auth.models import Permission - -import datetime -from dateutil.relativedelta import relativedelta from django.utils import timezone from users.models import Adherent -from .models import Vente, Facture, Cotisation, Paiement, Article + +from .models import Article, Cotisation, Facture, Paiement, Vente class NewFactureTests(TestCase): diff --git a/cotisations/tex.py b/cotisations/tex.py index fd6b6cc4..49697699 100644 --- a/cotisations/tex.py +++ b/cotisations/tex.py @@ -26,20 +26,19 @@ Used to generated PDF invoice. """ -import tempfile -from subprocess import Popen, PIPE import os +import tempfile from datetime import datetime +from subprocess import PIPE, Popen -from django.db import models -from django.template.loader import get_template -from django.http import HttpResponse from django.conf import settings +from django.db import models +from django.http import HttpResponse +from django.template.loader import get_template from django.utils.text import slugify -from re2o.mixins import AclMixin, RevMixin from preferences.models import CotisationsOption - +from re2o.mixins import AclMixin, RevMixin TEMP_PREFIX = getattr(settings, "TEX_TEMP_PREFIX", "render_tex-") CACHE_PREFIX = getattr(settings, "TEX_CACHE_PREFIX", "render-tex") diff --git a/cotisations/urls.py b/cotisations/urls.py index 9967b114..70a8fa92 100644 --- a/cotisations/urls.py +++ b/cotisations/urls.py @@ -27,23 +27,18 @@ from __future__ import unicode_literals from django.urls import path -from . import views, views_autocomplete -from . import payment_methods +from . import payment_methods, views, views_autocomplete -app_name ="cotisations" +app_name = "cotisations" urlpatterns = [ path("new_facture/", views.new_facture, name="new-facture"), - path( - "edit_facture/", views.edit_facture, name="edit-facture" - ), + path("edit_facture/", views.edit_facture, name="edit-facture"), path("del_facture/", views.del_facture, name="del-facture"), path("facture_pdf/", views.facture_pdf, name="facture-pdf"), path("voucher_pdf/", views.voucher_pdf, name="voucher-pdf"), path("new_cost_estimate", views.new_cost_estimate, name="new-cost-estimate"), - path( - "index_cost_estimate", views.index_cost_estimate, name="index-cost-estimate" - ), + path("index_cost_estimate", views.index_cost_estimate, name="index-cost-estimate"), path( "cost_estimate_pdf/", views.cost_estimate_pdf, @@ -87,9 +82,7 @@ urlpatterns = [ ), path("credit_solde/", views.credit_solde, name="credit-solde"), path("add_article", views.add_article, name="add-article"), - path( - "edit_article/", views.edit_article, name="edit-article" - ), + path("edit_article/", views.edit_article, name="edit-article"), path("del_article", views.del_article, name="del-article"), path("add_paiement", views.add_paiement, name="add-paiement"), path( @@ -107,5 +100,9 @@ urlpatterns = [ path("control", views.control, name="control"), path("", views.index, name="index"), ### Autocomplete Views - path('banque-autocomplete', views_autocomplete.BanqueAutocomplete.as_view(), name='banque-autocomplete',), + path( + "banque-autocomplete", + views_autocomplete.BanqueAutocomplete.as_view(), + name="banque-autocomplete", + ), ] + payment_methods.urls.urlpatterns diff --git a/cotisations/utils.py b/cotisations/utils.py index f484546a..b287f78d 100644 --- a/cotisations/utils.py +++ b/cotisations/utils.py @@ -21,14 +21,16 @@ import os -from django.template.loader import get_template from django.core.mail import EmailMessage +from django.template.loader import get_template + +from preferences.models import (AssoOption, CotisationsOption, GeneralOption, + Mandate) +from re2o import settings from re2o.mail_utils import send_mail_object +from re2o.settings import LOGO_PATH from .tex import create_pdf -from preferences.models import AssoOption, GeneralOption, CotisationsOption, Mandate -from re2o.settings import LOGO_PATH -from re2o import settings def find_payment_method(payment): @@ -74,7 +76,9 @@ def send_mail_invoice(invoice, request=None): "tpl_path": os.path.join(settings.BASE_DIR, LOGO_PATH), } - template = CotisationsOption.get_cached_value("invoice_template").template.name.split("/")[-1] + template = CotisationsOption.get_cached_value( + "invoice_template" + ).template.name.split("/")[-1] pdf = create_pdf(template, ctx) template = get_template("cotisations/email_invoice") @@ -106,7 +110,9 @@ def send_mail_voucher(invoice, request=None): "email": invoice.user.email, "phone": invoice.user.telephone, "date_end": invoice.get_subscription().latest("date_end_memb").date_end_memb, - "date_begin": invoice.get_subscription().earliest("date_start_memb").date_start_memb, + "date_begin": invoice.get_subscription() + .earliest("date_start_memb") + .date_start_memb, } templatename = CotisationsOption.get_cached_value( "voucher_template" diff --git a/cotisations/views.py b/cotisations/views.py index e114b569..d6fda3f5 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -29,62 +29,38 @@ The different views used in the Cotisations module """ from __future__ import unicode_literals + import os -from django.urls import reverse -from django.shortcuts import render, redirect, get_object_or_404 -from django.template.loader import render_to_string -from django.contrib.auth.decorators import login_required from django.contrib import messages -from django.db.models import ProtectedError -from django.db.models import Q -from django.forms import modelformset_factory, formset_factory +from django.contrib.auth.decorators import login_required +from django.db.models import ProtectedError, Q +from django.forms import formset_factory, modelformset_factory +from django.shortcuts import get_object_or_404, redirect, render +from django.template.loader import render_to_string +from django.urls import reverse from django.utils import timezone from django.utils.translation import ugettext as _ - # Import des models, forms et fonctions re2o from reversion import revisions as reversion -from users.models import User -from re2o.settings import LOGO_PATH -from re2o import settings -from re2o.views import form -from re2o.base import SortTable, re2o_paginator -from re2o.acl import ( - can_create, - can_edit, - can_delete, - can_view, - can_view_all, - can_delete_set, - can_change, -) + from preferences.models import AssoOption, GeneralOption, Mandate -from .models import ( - Facture, - Article, - Vente, - Paiement, - Banque, - CustomInvoice, - BaseInvoice, - CostEstimate, -) -from .forms import ( - FactureForm, - ArticleForm, - DelArticleForm, - PaiementForm, - DelPaiementForm, - BanqueForm, - DelBanqueForm, - SelectArticleForm, - RechargeForm, - CustomInvoiceForm, - DiscountForm, - CostEstimateForm, -) -from .tex import render_invoice, render_voucher, escape_chars +from re2o import settings +from re2o.acl import (can_change, can_create, can_delete, can_delete_set, + can_edit, can_view, can_view_all) +from re2o.base import SortTable, re2o_paginator +from re2o.settings import LOGO_PATH +from re2o.views import form +from users.models import User + +from .forms import (ArticleForm, BanqueForm, CostEstimateForm, + CustomInvoiceForm, DelArticleForm, DelBanqueForm, + DelPaiementForm, DiscountForm, FactureForm, PaiementForm, + RechargeForm, SelectArticleForm) +from .models import (Article, Banque, BaseInvoice, CostEstimate, CustomInvoice, + Facture, Paiement, Vente) from .payment_methods.forms import payment_method_factory +from .tex import escape_chars, render_invoice, render_voucher from .utils import find_payment_method @@ -136,7 +112,7 @@ def new_facture(request, user, userid): duration_membership=article.duration_membership, duration_days_membership=article.duration_days_membership, number=quantity, - ) + ) purchases.append(new_purchase) p = find_payment_method(new_invoice_instance.paiement) if hasattr(p, "check_price"): @@ -1057,12 +1033,15 @@ def voucher_pdf(request, invoice, **_kwargs): "lastname": invoice.user.surname, "email": invoice.user.email, "phone": invoice.user.telephone, - "date_end": invoice.get_subscription().latest("date_end_memb").date_end_memb, + "date_end": invoice.get_subscription() + .latest("date_end_memb") + .date_end_memb, "date_begin": invoice.date, }, ) -def aff_profil(request,user): + +def aff_profil(request, user): """View used to display the cotisations on a user's profil.""" factures = Facture.objects.filter(user=user) @@ -1071,16 +1050,16 @@ def aff_profil(request,user): request.GET.get("col"), request.GET.get("order"), SortTable.COTISATIONS_INDEX, - ) - + ) + pagination_large_number = GeneralOption.get_cached_value("pagination_large_number") - factures = re2o_paginator(request, factures,pagination_large_number) + factures = re2o_paginator(request, factures, pagination_large_number) context = { - "users":user, + "users": user, "facture_list": factures, - } + } return render_to_string( - "cotisations/aff_profil.html",context=context,request=request,using=None - ) + "cotisations/aff_profil.html", context=context, request=request, using=None + ) diff --git a/cotisations/views_autocomplete.py b/cotisations/views_autocomplete.py index 1e634813..bb89ace5 100644 --- a/cotisations/views_autocomplete.py +++ b/cotisations/views_autocomplete.py @@ -31,20 +31,13 @@ Here are defined the autocomplete class based view. """ from __future__ import unicode_literals -from django.db.models import Q, Value, CharField - -from .models import ( - Banque -) +from django.db.models import CharField, Q, Value +from re2o.acl import can_view_all from re2o.views import AutocompleteViewMixin -from re2o.acl import ( - can_view_all, -) +from .models import Banque class BanqueAutocomplete(AutocompleteViewMixin): obj_type = Banque - - diff --git a/freeradius_utils/auth.py b/freeradius_utils/auth.py index f4201f44..00c3e774 100644 --- a/freeradius_utils/auth.py +++ b/freeradius_utils/auth.py @@ -34,12 +34,12 @@ https://github.com/FreeRADIUS/freeradius-server/blob/master/src/modules/rlm_pyth Inspired by Daniel Stan in Crans """ +import logging import os import sys -import logging import traceback -import radiusd # Magic module freeradius (radiusd.py is dummy) +import radiusd # Magic module freeradius (radiusd.py is dummy) from django.core.wsgi import get_wsgi_application from django.db.models import Q @@ -54,11 +54,10 @@ os.chdir(proj_path) # This is so models get loaded. application = get_wsgi_application() -from machines.models import Interface, IpList, Nas, Domain +from machines.models import Domain, Interface, IpList, Nas +from preferences.models import RadiusOption from topologie.models import Port, Switch from users.models import User -from preferences.models import RadiusOption - # Logging @@ -76,7 +75,7 @@ class RadiusdHandler(logging.Handler): radiusd.radlog(rad_sig, str(record.msg)) -# Init for logging +# Init for logging logger = logging.getLogger("auth.py") logger.setLevel(logging.DEBUG) formatter = logging.Formatter("%(name)s: [%(levelname)s] %(message)s") @@ -132,10 +131,10 @@ def authorize(data): - If the nas is known, we apply the 802.1X if enabled, - It the nas is known AND nas auth is enabled with mac address, returns accept here""" - # For proxified request, split + # For proxified request, split nas = data.get("NAS-IP-Address", data.get("NAS-Identifier", None)) nas_instance = find_nas_from_request(nas) - # For none proxified requests + # For none proxified requests nas_type = None if nas_instance: nas_type = Nas.objects.filter(nas_type=nas_instance.machine_type).first() @@ -162,12 +161,11 @@ def authorize(data): @radius_event def post_auth(data): - """ Function called after the user is authenticated - """ + """Function called after the user is authenticated""" nas = data.get("NAS-IP-Address", data.get("NAS-Identifier", None)) nas_instance = find_nas_from_request(nas) - # All non proxified requests + # All non proxified requests if not nas_instance: logger.info("Proxified request, nas unknown") return radiusd.RLM_MODULE_OK @@ -309,7 +307,7 @@ def decide_vlan_switch(nas_machine, nas_type, port_number, mac_address): - no room : Decision set in Re2o RadiusOption, - no user in this room : Reject, - user of this room is banned or disable : Reject, - - user of this room non-contributor and not whitelisted: + - user of this room non-contributor and not whitelisted: Decision set in Re2o RadiusOption - mode common : - mac-address already registered: @@ -336,7 +334,7 @@ def decide_vlan_switch(nas_machine, nas_type, port_number, mac_address): } # Get port from switch and port number extra_log = "" - # If NAS is unknown, go to default vlan + # If NAS is unknown, go to default vlan if not nas_machine: return ( "?", @@ -366,7 +364,7 @@ def decide_vlan_switch(nas_machine, nas_type, port_number, mac_address): RadiusOption.get_cached_value("unknown_port") != RadiusOption.REJECT, RadiusOption.get_attributes("unknown_port_attributes", attributes_kwargs), ) - + # Retrieve port profile port_profile = port.get_port_profile diff --git a/ldap_sync/admin.py b/ldap_sync/admin.py index 0cba55da..ef859188 100644 --- a/ldap_sync/admin.py +++ b/ldap_sync/admin.py @@ -1,11 +1,8 @@ from django.contrib import admin -from .models import ( - LdapUser, - LdapServiceUser, - LdapServiceUserGroup, - LdapUserGroup, -) +from .models import (LdapServiceUser, LdapServiceUserGroup, LdapUser, + LdapUserGroup) + class LdapUserAdmin(admin.ModelAdmin): """LdapUser Admin view. Can't change password, manage @@ -15,6 +12,7 @@ class LdapUserAdmin(admin.ModelAdmin): Django ModelAdmin: Apply on django ModelAdmin """ + list_display = ("name", "uidNumber", "login_shell") exclude = ("user_password", "sambat_nt_password") search_fields = ("name",) diff --git a/ldap_sync/apps.py b/ldap_sync/apps.py index b96c34d1..ed06ca2a 100644 --- a/ldap_sync/apps.py +++ b/ldap_sync/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class LdapSyncConfig(AppConfig): - name = 'ldap_sync' + name = "ldap_sync" diff --git a/ldap_sync/management/commands/ldap_rebuild.py b/ldap_sync/management/commands/ldap_rebuild.py index 1fc3c969..dbe171eb 100644 --- a/ldap_sync/management/commands/ldap_rebuild.py +++ b/ldap_sync/management/commands/ldap_rebuild.py @@ -18,11 +18,12 @@ import subprocess from base64 import decodebytes -from django.core.management.base import BaseCommand, CommandError from django.conf import settings +from django.core.management.base import BaseCommand, CommandError -from users.models import User, ListRight -from ldap_sync.models import synchronise_user, synchronise_serviceuser, synchronise_usergroup +from ldap_sync.models import (synchronise_serviceuser, synchronise_user, + synchronise_usergroup) +from users.models import ListRight, User def split_lines(lines): diff --git a/ldap_sync/management/commands/ldap_sync.py b/ldap_sync/management/commands/ldap_sync.py index 984f3fd7..75af7738 100644 --- a/ldap_sync/management/commands/ldap_sync.py +++ b/ldap_sync/management/commands/ldap_sync.py @@ -19,8 +19,8 @@ # from django.core.management.base import BaseCommand, CommandError -from users.models import User from ldap_sync.models import synchronise_user +from users.models import User class Command(BaseCommand): diff --git a/ldap_sync/models.py b/ldap_sync/models.py index b360e7c1..ac3e69a7 100644 --- a/ldap_sync/models.py +++ b/ldap_sync/models.py @@ -1,18 +1,16 @@ import sys -from django.db import models -from django.conf import settings -from django.dispatch import receiver - -from django.contrib.auth.models import Group - import ldapdb.models import ldapdb.models.fields - -import users.signals -import users.models +from django.conf import settings +from django.contrib.auth.models import Group +from django.db import models +from django.dispatch import receiver import machines.models +import users.models +import users.signals + class LdapUser(ldapdb.models.Model): """A class representing a LdapUser in LDAP, its LDAP conterpart. @@ -110,13 +108,13 @@ def synchronise_user(sender, **kwargs): * mac_refresh : Default `True`. When True, synchronise the list of mac addresses. * group_refresh: Default `False`. When `True` synchronise the groups of the instance. """ - base=kwargs.get('base', True) - access_refresh=kwargs.get('access_refresh', True) - mac_refresh=kwargs.get('mac_refresh', True ) - group_refresh=kwargs.get('group_refresh', False) + base = kwargs.get("base", True) + access_refresh = kwargs.get("access_refresh", True) + mac_refresh = kwargs.get("mac_refresh", True) + group_refresh = kwargs.get("group_refresh", False) + + user = kwargs["instance"] - user=kwargs["instance"] - if sys.version_info[0] >= 3 and ( user.state == user.STATE_ACTIVE or user.state == user.STATE_ARCHIVE @@ -136,9 +134,7 @@ def synchronise_user(sender, **kwargs): user_ldap.dialupAccess = str(user.has_access()) user_ldap.home_directory = user.home_directory user_ldap.mail = user.get_mail - user_ldap.given_name = ( - user.surname.lower() + "_" + user.name.lower()[:3] - ) + user_ldap.given_name = user.surname.lower() + "_" + user.name.lower()[:3] user_ldap.gid = settings.LDAP["user_gid"] if "{SSHA}" in user.password or "{SMD5}" in user.password: # We remove the extra $ added at import from ldap @@ -169,9 +165,12 @@ def synchronise_user(sender, **kwargs): # be part of the updated group (case of group removal) for group in Group.objects.all(): if hasattr(group, "listright"): - synchronise_usergroup(users.models.ListRight, instance=group.listright) + synchronise_usergroup( + users.models.ListRight, instance=group.listright + ) user_ldap.save() + @receiver(users.signals.remove, sender=users.models.User) def remove_user(sender, **kwargs): user = kwargs["instance"] @@ -181,6 +180,7 @@ def remove_user(sender, **kwargs): except LdapUser.DoesNotExist: pass + @receiver(users.signals.remove_mass, sender=users.models.User) def remove_users(sender, **kwargs): queryset_users = kwargs["queryset"] @@ -217,6 +217,7 @@ class LdapUserGroup(ldapdb.models.Model): def __str__(self): return self.name + @receiver(users.signals.synchronise, sender=users.models.ListRight) def synchronise_usergroup(sender, **kwargs): group = kwargs["instance"] @@ -228,6 +229,7 @@ def synchronise_usergroup(sender, **kwargs): group_ldap.members = [user.pseudo for user in group.user_set.all()] group_ldap.save() + @receiver(users.signals.remove, sender=users.models.ListRight) def remove_usergroup(sender, **kwargs): group = kwargs["instance"] @@ -238,7 +240,6 @@ def remove_usergroup(sender, **kwargs): pass - class LdapServiceUser(ldapdb.models.Model): """A class representing a ServiceUser in LDAP, its LDAP conterpart. Synced from ServiceUser, with a copy of its attributes/fields into LDAP, @@ -296,6 +297,7 @@ def synchronise_serviceuser(sender, **kwargs): user_ldap.save() synchronise_serviceuser_group(user) + @receiver(users.signals.remove, sender=users.models.ServiceUser) def remove_serviceuser(sender, **kwargs): user = kwargs["instance"] @@ -331,4 +333,3 @@ class LdapServiceUserGroup(ldapdb.models.Model): def __str__(self): return self.name - diff --git a/ldap_sync/urls.py b/ldap_sync/urls.py index b9622e85..6af84065 100644 --- a/ldap_sync/urls.py +++ b/ldap_sync/urls.py @@ -1,5 +1,6 @@ from django.urls import path -from .import views + +from . import views app_name = "ldap_sync" diff --git a/logs/apps.py b/logs/apps.py index 255ad039..0badfaae 100644 --- a/logs/apps.py +++ b/logs/apps.py @@ -8,4 +8,4 @@ from django.apps import AppConfig class LogsConfig(AppConfig): """Configuration of logs app.""" - name = "logs" \ No newline at end of file + name = "logs" diff --git a/logs/forms.py b/logs/forms.py index 0a2ca09c..989d5e7e 100644 --- a/logs/forms.py +++ b/logs/forms.py @@ -21,13 +21,11 @@ """The forms used by the machine search view""" +import inspect + from django import forms from django.forms import Form from django.utils.translation import ugettext_lazy as _ -from re2o.base import get_input_formats_help_text -from re2o.widgets import AutocompleteModelWidget - -import inspect # Import all models in which there are classes to be filtered on import cotisations.models @@ -35,7 +33,8 @@ import machines.models import preferences.models import topologie.models import users.models - +from re2o.base import get_input_formats_help_text +from re2o.widgets import AutocompleteModelWidget CHOICES_ACTION_TYPE = ( ("users", _("Users")), diff --git a/logs/models.py b/logs/models.py index 51b250f3..97b4201d 100644 --- a/logs/models.py +++ b/logs/models.py @@ -21,23 +21,17 @@ """logs.models The models definitions for the logs app """ -from reversion.models import Version, Revision -from django.utils.translation import ugettext_lazy as _ +from django.apps import apps from django.contrib.auth.models import Group from django.db.models import Q -from django.apps import apps -from netaddr import EUI +from django.utils.translation import ugettext_lazy as _ from macaddress.fields import default_dialect +from netaddr import EUI +from reversion.models import Revision, Version -from machines.models import IpList -from machines.models import Interface -from machines.models import Machine -from machines.models import MachineType -from users.models import User -from users.models import Adherent -from users.models import Club -from topologie.models import Room -from topologie.models import Port +from machines.models import Interface, IpList, Machine, MachineType +from topologie.models import Port, Room +from users.models import Adherent, Club, User from .forms import classes_for_action_type @@ -53,13 +47,12 @@ def make_version_filter(key, value): # The lookup is done in a json string, so it has to be formated # based on the value's type (to add " or not) if type(value) is str: - formatted_value = "\"{}\"".format(value) + formatted_value = '"{}"'.format(value) else: formatted_value = str(value) - return ( - Q(serialized_data__contains='\"{}\": {},'.format(key, formatted_value)) - | Q(serialized_data__contains='\"{}\": {}}}'.format(key, formatted_value)) + return Q(serialized_data__contains='"{}": {},'.format(key, formatted_value)) | Q( + serialized_data__contains='"{}": {}}}'.format(key, formatted_value) ) @@ -67,6 +60,7 @@ def make_version_filter(key, value): # Machine history search # ############################ + class MachineHistorySearchEvent: def __init__(self, user, machine, interface, start=None, end=None): """Initialise an instance of MachineHistorySearchEvent. @@ -113,7 +107,7 @@ class MachineHistorySearchEvent: self.ipv4, self.start_date, self.end_date, - self.comment or "No comment" + self.comment or "No comment", ) @@ -300,6 +294,7 @@ class MachineHistorySearch: # Generic history classes # ############################ + class RelatedHistory: def __init__(self, version): """Initialise an instance of RelatedHistory. @@ -317,10 +312,7 @@ class RelatedHistory: self.name = "{}: {}".format(self.model_name.title(), self.name) def __eq__(self, other): - return ( - self.model_name == other.model_name - and self.object_id == other.object_id - ) + return self.model_name == other.model_name and self.object_id == other.object_id def __hash__(self): return hash((self.model_name, self.object_id)) @@ -382,15 +374,11 @@ class HistoryEvent: # Take into account keys that may exist in only one dict if field in self.previous_version.field_dict: old_value = self._repr( - field, - self.previous_version.field_dict[field] + field, self.previous_version.field_dict[field] ) if field in self.version.field_dict: - new_value = self._repr( - field, - self.version.field_dict[field] - ) + new_value = self._repr(field, self.version.field_dict[field]) edits.append((field, old_value, new_value)) @@ -487,6 +475,7 @@ class History: # Revision history # ############################ + class VersionAction(HistoryEvent): def __init__(self, version): self.version = version @@ -533,15 +522,14 @@ class VersionAction(HistoryEvent): """ model = self.object_type() try: - query = ( - make_version_filter("pk", self.object_id()) - & Q( - revision__date_created__lt=self.version.revision.date_created - ) + query = make_version_filter("pk", self.object_id()) & Q( + revision__date_created__lt=self.version.revision.date_created + ) + return ( + Version.objects.get_for_model(model) + .filter(query) + .order_by("-revision__date_created")[0] ) - return (Version.objects.get_for_model(model) - .filter(query) - .order_by("-revision__date_created")[0]) except Exception: return None @@ -648,6 +636,7 @@ class ActionsSearch: # Class-specific history # ############################ + class UserHistoryEvent(HistoryEvent): def _repr(self, name, value): """Get the appropriate representation of the given field. @@ -733,13 +722,15 @@ class UserHistoryEvent(HistoryEvent): ) def __hash__(self): - return hash((frozenset(self.edited_fields), self.date, self.performed_by, self.comment)) + return hash( + (frozenset(self.edited_fields), self.date, self.performed_by, self.comment) + ) def __repr__(self): return "{} edited fields {} ({})".format( self.performed_by, self.edited_fields or "nothing", - self.comment or "No comment" + self.comment or "No comment", ) @@ -762,9 +753,8 @@ class UserHistory(History): # Try to find an Adherent object # If it exists, its id will be the same as the user's - adherents = ( - Version.objects.get_for_model(Adherent) - .filter(make_version_filter("pk", user_id)) + adherents = Version.objects.get_for_model(Adherent).filter( + make_version_filter("pk", user_id) ) try: obj = adherents[0] @@ -774,9 +764,8 @@ class UserHistory(History): # Fallback on a Club if obj is None: - clubs = ( - Version.objects.get_for_model(Club) - .filter(make_version_filter("pk", user_id)) + clubs = Version.objects.get_for_model(Club).filter( + make_version_filter("pk", user_id) ) try: @@ -826,11 +815,7 @@ class UserHistory(History): # Remove duplicates and sort self.events = list(dict.fromkeys(self.events)) - return sorted( - self.events, - key=lambda e: e.date, - reverse=True - ) + return sorted(self.events, key=lambda e: e.date, reverse=True) def _add_revision(self, version): """Add a new revision to the chronological order. @@ -843,7 +828,7 @@ class UserHistory(History): diff = self._compute_diff( version, self._last_version, - ignoring=["last_login", "pwd_ntlm", "email_change_date"] + ignoring=["last_login", "pwd_ntlm", "email_change_date"], ) # Ignore "empty" events like login @@ -973,7 +958,7 @@ HISTORY_CLASS_MAPPING = { User: UserHistory, Machine: MachineHistory, Interface: InterfaceHistory, - "default": History + "default": History, } diff --git a/logs/urls.py b/logs/urls.py index d87a4efc..880d7486 100644 --- a/logs/urls.py +++ b/logs/urls.py @@ -47,5 +47,9 @@ urlpatterns = [ path("stats_models", views.stats_models, name="stats-models"), path("stats_users", views.stats_users, name="stats-users"), path("stats_actions", views.stats_actions, name="stats-actions"), - path("stats_search_machine", views.stats_search_machine_history, name="stats-search-machine"), + path( + "stats_search_machine", + views.stats_search_machine_history, + name="stats-search-machine", + ), ] diff --git a/logs/views.py b/logs/views.py index 7b01c4a5..66d1a99d 100644 --- a/logs/views.py +++ b/logs/views.py @@ -38,84 +38,38 @@ objects for per model, number of actions per user etc. from __future__ import unicode_literals -from django.urls import reverse -from django.shortcuts import render, redirect +from django.apps import apps from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.http import Http404 from django.db.models import Count -from django.apps import apps +from django.http import Http404 +from django.shortcuts import redirect, render +from django.urls import reverse from django.utils.translation import ugettext as _ +from reversion.models import ContentType, Revision, Version -from reversion.models import Revision -from reversion.models import Version, ContentType - -from users.models import ( - User, - ServiceUser, - School, - ListRight, - ListShell, - Ban, - Whitelist, - Adherent, - Club, -) -from cotisations.models import Facture, Vente, Article, Banque, Paiement, Cotisation -from machines.models import ( - Machine, - MachineType, - IpType, - Extension, - Interface, - Domain, - IpList, - OuverturePortList, - Service, - Vlan, - Nas, - SOA, - Mx, - Ns, -) -from topologie.models import ( - Switch, - Port, - Room, - Stack, - ModelSwitch, - ConstructorSwitch, - AccessPoint, -) +from cotisations.models import (Article, Banque, Cotisation, Facture, Paiement, + Vente) +from machines.models import (SOA, Domain, Extension, Interface, IpList, IpType, + Machine, MachineType, Mx, Nas, Ns, + OuverturePortList, Service, Vlan) from preferences.models import GeneralOption +from re2o.acl import (acl_error_message, can_edit_history, can_view, + can_view_all, can_view_app) +from re2o.base import SortTable, re2o_paginator +from re2o.utils import (all_active_assigned_interfaces_count, + all_active_interfaces_count, all_adherent, all_baned, + all_has_access, all_whitelisted) from re2o.views import form -from re2o.utils import ( - all_whitelisted, - all_baned, - all_has_access, - all_adherent, - all_active_assigned_interfaces_count, - all_active_interfaces_count, -) -from re2o.base import re2o_paginator, SortTable -from re2o.acl import ( - can_view_all, - can_view_app, - can_edit_history, - can_view, - acl_error_message, -) - -from .models import ( - ActionsSearch, - RevisionAction, - MachineHistorySearch, - get_history_class, -) - -from .forms import ActionsSearchForm, MachineHistorySearchForm +from topologie.models import (AccessPoint, ConstructorSwitch, ModelSwitch, + Port, Room, Stack, Switch) +from users.models import (Adherent, Ban, Club, ListRight, ListShell, School, + ServiceUser, User, Whitelist) from .acl import can_view as can_view_logs +from .forms import ActionsSearchForm, MachineHistorySearchForm +from .models import (ActionsSearch, MachineHistorySearch, RevisionAction, + get_history_class) @login_required @@ -528,7 +482,11 @@ def stats_search_machine_history(request): max_result = GeneralOption.get_cached_value("pagination_number") events = re2o_paginator(request, events, max_result) - return render(request, "logs/machine_history.html", {"events": events},) + return render( + request, + "logs/machine_history.html", + {"events": events}, + ) return render( request, "logs/search_machine_history.html", {"history_form": history_form} ) diff --git a/machines/admin.py b/machines/admin.py index dc2817f0..6306e00f 100644 --- a/machines/admin.py +++ b/machines/admin.py @@ -29,24 +29,10 @@ from __future__ import unicode_literals from django.contrib import admin from reversion.admin import VersionAdmin -from .models import ( - Extension, - SOA, - Mx, - Ns, - Vlan, - Txt, - DName, - Srv, - SshFp, - Nas, - Service, - Role, - OuverturePort, - Ipv6List, - OuverturePortList, -) -from .models import IpType, Machine, MachineType, Domain, IpList, Interface +from .models import (SOA, DName, Domain, Extension, Interface, IpList, IpType, + Ipv6List, Machine, MachineType, Mx, Nas, Ns, + OuverturePort, OuverturePortList, Role, Service, Srv, + SshFp, Txt, Vlan) class MachineAdmin(VersionAdmin): diff --git a/machines/api/serializers.py b/machines/api/serializers.py index 0c8b7ffe..bfbed72e 100644 --- a/machines/api/serializers.py +++ b/machines/api/serializers.py @@ -22,12 +22,12 @@ from rest_framework import serializers import machines.models as machines -from api.serializers import NamespacedHRField, NamespacedHIField, NamespacedHMSerializer +from api.serializers import (NamespacedHIField, NamespacedHMSerializer, + NamespacedHRField) class MachineSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Machine` objects. - """ + """Serialize `machines.models.Machine` objects.""" class Meta: model = machines.Machine @@ -35,8 +35,7 @@ class MachineSerializer(NamespacedHMSerializer): class MachineTypeSerializer(NamespacedHMSerializer): - """Serialize `machines.models.MachineType` objects. - """ + """Serialize `machines.models.MachineType` objects.""" class Meta: model = machines.MachineType @@ -44,8 +43,7 @@ class MachineTypeSerializer(NamespacedHMSerializer): class IpTypeSerializer(NamespacedHMSerializer): - """Serialize `machines.models.IpType` objects. - """ + """Serialize `machines.models.IpType` objects.""" class Meta: model = machines.IpType @@ -63,8 +61,7 @@ class IpTypeSerializer(NamespacedHMSerializer): class VlanSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Vlan` objects. - """ + """Serialize `machines.models.Vlan` objects.""" class Meta: model = machines.Vlan @@ -82,8 +79,7 @@ class VlanSerializer(NamespacedHMSerializer): class NasSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Nas` objects. - """ + """Serialize `machines.models.Nas` objects.""" class Meta: model = machines.Nas @@ -98,8 +94,7 @@ class NasSerializer(NamespacedHMSerializer): class SOASerializer(NamespacedHMSerializer): - """Serialize `machines.models.SOA` objects. - """ + """Serialize `machines.models.SOA` objects.""" class Meta: model = machines.SOA @@ -107,8 +102,7 @@ class SOASerializer(NamespacedHMSerializer): class ExtensionSerializer(NamespacedHMSerializer): - """Serialize machines.models.Extension objects. - """ + """Serialize machines.models.Extension objects.""" class Meta: model = machines.Extension @@ -116,8 +110,7 @@ class ExtensionSerializer(NamespacedHMSerializer): class MxSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Mx` objects. - """ + """Serialize `machines.models.Mx` objects.""" class Meta: model = machines.Mx @@ -125,8 +118,7 @@ class MxSerializer(NamespacedHMSerializer): class DNameSerializer(NamespacedHMSerializer): - """Serialize `machines.models.DName` objects. - """ + """Serialize `machines.models.DName` objects.""" class Meta: model = machines.DName @@ -134,8 +126,7 @@ class DNameSerializer(NamespacedHMSerializer): class NsSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Ns` objects. - """ + """Serialize `machines.models.Ns` objects.""" class Meta: model = machines.Ns @@ -143,8 +134,7 @@ class NsSerializer(NamespacedHMSerializer): class TxtSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Txt` objects. - """ + """Serialize `machines.models.Txt` objects.""" class Meta: model = machines.Txt @@ -152,8 +142,7 @@ class TxtSerializer(NamespacedHMSerializer): class SrvSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Srv` objects. - """ + """Serialize `machines.models.Srv` objects.""" class Meta: model = machines.Srv @@ -171,8 +160,7 @@ class SrvSerializer(NamespacedHMSerializer): class SshFpSerializer(NamespacedHMSerializer): - """Serialize `machines.models.SSHFP` objects. - """ + """Serialize `machines.models.SSHFP` objects.""" class Meta: model = machines.SshFp @@ -180,8 +168,7 @@ class SshFpSerializer(NamespacedHMSerializer): class InterfaceSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Interface` objects. - """ + """Serialize `machines.models.Interface` objects.""" mac_address = serializers.CharField() active = serializers.BooleanField(source="is_active") @@ -201,8 +188,7 @@ class InterfaceSerializer(NamespacedHMSerializer): class Ipv6ListSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Ipv6List` objects. - """ + """Serialize `machines.models.Ipv6List` objects.""" class Meta: model = machines.Ipv6List @@ -210,8 +196,7 @@ class Ipv6ListSerializer(NamespacedHMSerializer): class DomainSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Domain` objects. - """ + """Serialize `machines.models.Domain` objects.""" class Meta: model = machines.Domain @@ -219,8 +204,7 @@ class DomainSerializer(NamespacedHMSerializer): class IpListSerializer(NamespacedHMSerializer): - """Serialize `machines.models.IpList` objects. - """ + """Serialize `machines.models.IpList` objects.""" class Meta: model = machines.IpList @@ -228,8 +212,7 @@ class IpListSerializer(NamespacedHMSerializer): class ServiceSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Service` objects. - """ + """Serialize `machines.models.Service` objects.""" class Meta: model = machines.Service @@ -243,8 +226,7 @@ class ServiceSerializer(NamespacedHMSerializer): class ServiceLinkSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Service_link` objects. - """ + """Serialize `machines.models.Service_link` objects.""" class Meta: model = machines.Service_link @@ -260,8 +242,7 @@ class ServiceLinkSerializer(NamespacedHMSerializer): class OuverturePortListSerializer(NamespacedHMSerializer): - """Serialize `machines.models.OuverturePortList` objects. - """ + """Serialize `machines.models.OuverturePortList` objects.""" tcp_ports_in = NamespacedHRField( view_name="ouvertureport-detail", many=True, read_only=True @@ -289,8 +270,7 @@ class OuverturePortListSerializer(NamespacedHMSerializer): class OuverturePortSerializer(NamespacedHMSerializer): - """Serialize `machines.models.OuverturePort` objects. - """ + """Serialize `machines.models.OuverturePort` objects.""" class Meta: model = machines.OuverturePort @@ -298,8 +278,7 @@ class OuverturePortSerializer(NamespacedHMSerializer): class RoleSerializer(NamespacedHMSerializer): - """Serialize `machines.models.OuverturePort` objects. - """ + """Serialize `machines.models.OuverturePort` objects.""" servers = InterfaceSerializer(read_only=True, many=True) @@ -309,8 +288,7 @@ class RoleSerializer(NamespacedHMSerializer): class ServiceRegenSerializer(NamespacedHMSerializer): - """Serialize the data about the services to regen. - """ + """Serialize the data about the services to regen.""" hostname = serializers.CharField(source="server.domain.name", read_only=True) service_name = serializers.CharField(source="service.service_type", read_only=True) @@ -517,8 +495,7 @@ class DNAMERecordSerializer(serializers.ModelSerializer): class DNSZonesSerializer(serializers.ModelSerializer): - """Serialize the data about DNS Zones. - """ + """Serialize the data about DNS Zones.""" soa = SOARecordSerializer() ns_records = NSRecordSerializer(many=True, source="ns_set") @@ -559,8 +536,7 @@ class DNSZonesSerializer(serializers.ModelSerializer): class DNSReverseZonesSerializer(serializers.ModelSerializer): - """Serialize the data about DNS Zones. - """ + """Serialize the data about DNS Zones.""" soa = SOARecordSerializer(source="extension.soa") extension = serializers.CharField(source="extension.name", read_only=True) diff --git a/machines/api/urls.py b/machines/api/urls.py index 3542e28c..d76187f1 100644 --- a/machines/api/urls.py +++ b/machines/api/urls.py @@ -45,9 +45,8 @@ urls_viewset = [ (r"machines/ouvertureport", views.OuverturePortViewSet, None), (r"machines/role", views.RoleViewSet, None), (r"machines/services-regen", views.ServiceRegenViewSet, "serviceregen"), - # Deprecated - (r"services/regen", views.ServiceRegenViewSet, "serviceregen") + (r"services/regen", views.ServiceRegenViewSet, "serviceregen"), ] urls_view = [ @@ -56,11 +55,10 @@ urls_view = [ (r"machines/firewall-interface-ports", views.InterfacePortsOpenView), (r"machines/dns-zones", views.DNSZonesView), (r"machines/dns-reverse-zones", views.DNSReverseZonesView), - # Deprecated (r"dhcp/hostmacip", views.HostMacIpView), (r"firewall/subnet-ports", views.SubnetPortsOpenView), (r"firewall/interface-ports", views.InterfacePortsOpenView), (r"dns/zones", views.DNSZonesView), (r"dns/reverse-zones", views.DNSReverseZonesView), -] \ No newline at end of file +] diff --git a/machines/api/views.py b/machines/api/views.py index 4fa23d8e..8d30bf53 100644 --- a/machines/api/views.py +++ b/machines/api/views.py @@ -19,159 +19,142 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from rest_framework import viewsets, generics +from rest_framework import generics, viewsets -from . import serializers import machines.models as machines from re2o.utils import all_active_interfaces +from . import serializers + + class MachineViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Machine` objects. - """ + """Exposes list and details of `machines.models.Machine` objects.""" queryset = machines.Machine.objects.all() serializer_class = serializers.MachineSerializer class MachineTypeViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.MachineType` objects. - """ + """Exposes list and details of `machines.models.MachineType` objects.""" queryset = machines.MachineType.objects.all() serializer_class = serializers.MachineTypeSerializer class IpTypeViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.IpType` objects. - """ + """Exposes list and details of `machines.models.IpType` objects.""" queryset = machines.IpType.objects.all() serializer_class = serializers.IpTypeSerializer class VlanViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Vlan` objects. - """ + """Exposes list and details of `machines.models.Vlan` objects.""" queryset = machines.Vlan.objects.all() serializer_class = serializers.VlanSerializer class NasViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Nas` objects. - """ + """Exposes list and details of `machines.models.Nas` objects.""" queryset = machines.Nas.objects.all() serializer_class = serializers.NasSerializer class SOAViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.SOA` objects. - """ + """Exposes list and details of `machines.models.SOA` objects.""" queryset = machines.SOA.objects.all() serializer_class = serializers.SOASerializer class ExtensionViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Extension` objects. - """ + """Exposes list and details of `machines.models.Extension` objects.""" queryset = machines.Extension.objects.all() serializer_class = serializers.ExtensionSerializer class MxViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Mx` objects. - """ + """Exposes list and details of `machines.models.Mx` objects.""" queryset = machines.Mx.objects.all() serializer_class = serializers.MxSerializer class NsViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Ns` objects. - """ + """Exposes list and details of `machines.models.Ns` objects.""" queryset = machines.Ns.objects.all() serializer_class = serializers.NsSerializer class TxtViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Txt` objects. - """ + """Exposes list and details of `machines.models.Txt` objects.""" queryset = machines.Txt.objects.all() serializer_class = serializers.TxtSerializer class DNameViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.DName` objects. - """ + """Exposes list and details of `machines.models.DName` objects.""" queryset = machines.DName.objects.all() serializer_class = serializers.DNameSerializer class SrvViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Srv` objects. - """ + """Exposes list and details of `machines.models.Srv` objects.""" queryset = machines.Srv.objects.all() serializer_class = serializers.SrvSerializer class SshFpViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.SshFp` objects. - """ + """Exposes list and details of `machines.models.SshFp` objects.""" queryset = machines.SshFp.objects.all() serializer_class = serializers.SshFpSerializer class InterfaceViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Interface` objects. - """ + """Exposes list and details of `machines.models.Interface` objects.""" queryset = machines.Interface.objects.all() serializer_class = serializers.InterfaceSerializer class Ipv6ListViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Ipv6List` objects. - """ + """Exposes list and details of `machines.models.Ipv6List` objects.""" queryset = machines.Ipv6List.objects.all() serializer_class = serializers.Ipv6ListSerializer class DomainViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Domain` objects. - """ + """Exposes list and details of `machines.models.Domain` objects.""" queryset = machines.Domain.objects.all() serializer_class = serializers.DomainSerializer class IpListViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.IpList` objects. - """ + """Exposes list and details of `machines.models.IpList` objects.""" queryset = machines.IpList.objects.all() serializer_class = serializers.IpListSerializer class ServiceViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Service` objects. - """ + """Exposes list and details of `machines.models.Service` objects.""" queryset = machines.Service.objects.all() serializer_class = serializers.ServiceSerializer class ServiceLinkViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Service_link` objects. - """ + """Exposes list and details of `machines.models.Service_link` objects.""" queryset = machines.Service_link.objects.all() serializer_class = serializers.ServiceLinkSerializer @@ -187,24 +170,21 @@ class OuverturePortListViewSet(viewsets.ReadOnlyModelViewSet): class OuverturePortViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.OuverturePort` objects. - """ + """Exposes list and details of `machines.models.OuverturePort` objects.""" queryset = machines.OuverturePort.objects.all() serializer_class = serializers.OuverturePortSerializer class RoleViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `machines.models.Machine` objects. - """ + """Exposes list and details of `machines.models.Machine` objects.""" queryset = machines.Role.objects.all() serializer_class = serializers.RoleSerializer class ServiceRegenViewSet(viewsets.ModelViewSet): - """Exposes list and details of the services to regen - """ + """Exposes list and details of the services to regen""" serializer_class = serializers.ServiceRegenSerializer @@ -238,6 +218,7 @@ class InterfacePortsOpenView(generics.ListAPIView): queryset = machines.Interface.objects.filter(port_lists__isnull=False).distinct() serializer_class = serializers.InterfacePortsOpenSerializer + class DNSZonesView(generics.ListAPIView): """Exposes the detailed information about each extension (hostnames, IPs, DNS records, etc.) in order to build the DNS zone files. @@ -264,4 +245,4 @@ class DNSReverseZonesView(generics.ListAPIView): """ queryset = machines.IpType.objects.all() - serializer_class = serializers.DNSReverseZonesSerializer \ No newline at end of file + serializer_class = serializers.DNSReverseZonesSerializer diff --git a/machines/apps.py b/machines/apps.py index 4ba9970c..f7f96755 100644 --- a/machines/apps.py +++ b/machines/apps.py @@ -8,4 +8,4 @@ from django.apps import AppConfig class MachinesConfig(AppConfig): """Configuration of machines app.""" - name = "machines" \ No newline at end of file + name = "machines" diff --git a/machines/forms.py b/machines/forms.py index ed5975e3..e2d3e1b1 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -36,37 +36,17 @@ Forms to create, edit and delete: from __future__ import unicode_literals from django import forms -from django.forms import ModelForm, Form +from django.forms import Form, ModelForm from django.utils.translation import ugettext_lazy as _ from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin -from re2o.widgets import ( - AutocompleteModelWidget, - AutocompleteMultipleModelWidget, -) -from .models import ( - Domain, - Machine, - Interface, - IpList, - MachineType, - Extension, - SOA, - Mx, - Txt, - DName, - Ns, - Role, - Service, - Vlan, - Srv, - SshFp, - Nas, - IpType, - OuverturePortList, - Ipv6List, -) +from re2o.widgets import (AutocompleteModelWidget, + AutocompleteMultipleModelWidget) + +from .models import (SOA, DName, Domain, Extension, Interface, IpList, IpType, + Ipv6List, Machine, MachineType, Mx, Nas, Ns, + OuverturePortList, Role, Service, Srv, SshFp, Txt, Vlan) class EditMachineForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): @@ -248,7 +228,9 @@ class IpTypeForm(FormRevMixin, ModelForm): fields = "__all__" widgets = { "vlan": AutocompleteModelWidget(url="/machines/vlan-autocomplete"), - "extension": AutocompleteModelWidget(url="/machines/extension-autocomplete"), + "extension": AutocompleteModelWidget( + url="/machines/extension-autocomplete" + ), "ouverture_ports": AutocompleteModelWidget( url="/machines/ouvertureportlist-autocomplete" ), @@ -525,7 +507,9 @@ class SrvForm(FormRevMixin, ModelForm): model = Srv fields = "__all__" widgets = { - "extension": AutocompleteModelWidget(url="/machines/extension-autocomplete"), + "extension": AutocompleteModelWidget( + url="/machines/extension-autocomplete" + ), "target": AutocompleteModelWidget(url="/machines/domain-autocomplete"), } diff --git a/machines/models.py b/machines/models.py index 8e4e494f..302d6bd3 100644 --- a/machines/models.py +++ b/machines/models.py @@ -35,26 +35,18 @@ from ipaddress import IPv6Address from itertools import chain from django.core.validators import MaxValueValidator, MinValueValidator -from django.db import models +from django.db import models, transaction from django.db.models import Q -from django.db.models.signals import post_save, post_delete +from django.db.models.signals import post_delete, post_save from django.dispatch import receiver from django.forms import ValidationError from django.utils import timezone -from django.db import transaction -from reversion import revisions as reversion from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from macaddress.fields import MACAddressField, default_dialect -from netaddr import ( - mac_bare, - EUI, - NotRegisteredError, - IPSet, - IPRange, - IPNetwork, - IPAddress, -) +from netaddr import (EUI, IPAddress, IPNetwork, IPRange, IPSet, + NotRegisteredError, mac_bare) +from reversion import revisions as reversion import preferences.models import users.models @@ -79,9 +71,7 @@ class Machine(RevMixin, FieldPermissionModelMixin, AclMixin, models.Model): active = models.BooleanField(default=True) class Meta: - permissions = ( - ("change_machine_user", _("Can change the user of a machine")), - ) + permissions = (("change_machine_user", _("Can change the user of a machine")),) verbose_name = _("machine") verbose_name_plural = _("machines") @@ -342,9 +332,7 @@ class MachineType(RevMixin, AclMixin, models.Model): ) class Meta: - permissions = ( - ("use_all_machinetype", _("Can use all machine types")), - ) + permissions = (("use_all_machinetype", _("Can use all machine types")),) verbose_name = _("machine type") verbose_name_plural = _("machine types") @@ -356,7 +344,9 @@ class MachineType(RevMixin, AclMixin, models.Model): """Update domains extension with the extension of interface_parent. Called after update of an ip_type or a machine_type object. Exceptions are handled in the views. (Calling domain.clear() for all domains could take several minutes) """ - Domain.objects.filter(interface_parent__machine_type=self).update(extension=self.ip_type.extension) + Domain.objects.filter(interface_parent__machine_type=self).update( + extension=self.ip_type.extension + ) @staticmethod def can_use_all(user_request, *_args, **_kwargs): @@ -379,7 +369,7 @@ class MachineType(RevMixin, AclMixin, models.Model): @classmethod def can_list(cls, user_request, *_args, **_kwargs): - """All users can list unprivileged machinetypes + """All users can list unprivileged machinetypes Only members of privileged groups can list all. :param user_request: The user who wants to view the list. @@ -389,20 +379,13 @@ class MachineType(RevMixin, AclMixin, models.Model): """ can, _message, _group = cls.can_use_all(user_request) if can: - return ( - True, - None, - None, - cls.objects.all() - ) + return (True, None, None, cls.objects.all()) else: return ( True, _("You don't have the right to use all machine types."), ("machines.use_all_machinetype",), - cls.objects.filter( - ip_type__in=IpType.objects.filter(need_infra=False) - ), + cls.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)), ) def __str__(self): @@ -455,12 +438,12 @@ class IpType(RevMixin, AclMixin, models.Model): default=False, help_text=_("Enable reverse DNS for IPv6.") ) vlan = models.ForeignKey("Vlan", on_delete=models.PROTECT, blank=True, null=True) - ouverture_ports = models.ForeignKey("OuverturePortList", blank=True, null=True, on_delete=models.PROTECT) + ouverture_ports = models.ForeignKey( + "OuverturePortList", blank=True, null=True, on_delete=models.PROTECT + ) class Meta: - permissions = ( - ("use_all_iptype", _("Can use all IP types")), - ) + permissions = (("use_all_iptype", _("Can use all IP types")),) verbose_name = _("IP type") verbose_name_plural = _("IP types") @@ -897,9 +880,7 @@ class Extension(RevMixin, AclMixin, models.Model): ) class Meta: - permissions = ( - ("use_all_extension", _("Can use all extensions")), - ) + permissions = (("use_all_extension", _("Can use all extensions")),) verbose_name = _("DNS extension") verbose_name_plural = _("DNS extensions") @@ -977,7 +958,7 @@ class Extension(RevMixin, AclMixin, models.Model): @classmethod def can_list(cls, user_request, *_args, **_kwargs): - """All users can list unprivileged extensions + """All users can list unprivileged extensions Only members of privileged groups can list all. :param user_request: The user who wants to view the list. @@ -987,12 +968,7 @@ class Extension(RevMixin, AclMixin, models.Model): """ can, _message, _group = cls.can_use_all(user_request) if can: - return ( - True, - None, - None, - cls.objects.all() - ) + return (True, None, None, cls.objects.all()) else: return ( True, @@ -1035,8 +1011,7 @@ class Mx(RevMixin, AclMixin, models.Model): @cached_property def dns_entry(self): - """Get the complete DNS entry of the MX record, to put in zone files. - """ + """Get the complete DNS entry of the MX record, to put in zone files.""" return "@ IN MX {prior} {name}".format( prior=str(self.priority).ljust(3), name=str(self.name) ) @@ -1066,8 +1041,7 @@ class Ns(RevMixin, AclMixin, models.Model): @cached_property def dns_entry(self): - """Get the complete DNS entry of the NS record, to put in zone files. - """ + """Get the complete DNS entry of the NS record, to put in zone files.""" return "@ IN NS " + str(self.ns) def __str__(self): @@ -1100,8 +1074,7 @@ class Txt(RevMixin, AclMixin, models.Model): @cached_property def dns_entry(self): - """Get the complete DNS entry of the TXT record, to put in zone files. - """ + """Get the complete DNS entry of the TXT record, to put in zone files.""" return str(self.field1).ljust(15) + " IN TXT " + str(self.field2) @@ -1129,8 +1102,7 @@ class DName(RevMixin, AclMixin, models.Model): @cached_property def dns_entry(self): - """Get the complete DNS entry of the TXT record, to put in zone files. - """ + """Get the complete DNS entry of the TXT record, to put in zone files.""" return str(self.alias).ljust(15) + " IN DNAME " + str(self.zone) @@ -1204,8 +1176,7 @@ class Srv(RevMixin, AclMixin, models.Model): @cached_property def dns_entry(self): - """Get the complete DNS entry of the SRV record, to put in zone files. - """ + """Get the complete DNS entry of the SRV record, to put in zone files.""" return ( str(self.service) + "._" @@ -1387,8 +1358,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): return vendor def sync_ipv6_dhcpv6(self): - """Assign an IPv6 address by DHCPv6, computed from the interface's ID. - """ + """Assign an IPv6 address by DHCPv6, computed from the interface's ID.""" ipv6_dhcpv6 = self.gen_ipv6_dhcpv6 if not ipv6_dhcpv6: return @@ -1414,8 +1384,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): ipv6_object.save() def sync_ipv6(self): - """Create and update the IPv6 addresses according to the IPv6 mode set. - """ + """Create and update the IPv6 addresses according to the IPv6 mode set.""" if preferences.models.OptionalMachine.get_cached_value("ipv6_mode") == "SLAAC": self.sync_ipv6_slaac() elif ( @@ -1581,8 +1550,10 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): _("You don't have the right to add a machine."), ("machines.add_interface",), ) - max_lambdauser_interfaces = preferences.models.OptionalMachine.get_cached_value( - "max_lambdauser_interfaces" + max_lambdauser_interfaces = ( + preferences.models.OptionalMachine.get_cached_value( + "max_lambdauser_interfaces" + ) ) if machine.user != user_request: return ( @@ -1721,8 +1692,7 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): ) slaac_ip = models.BooleanField(default=False) active = models.BooleanField( - default=True, - help_text=_("If false,the DNS will not provide this ip.") + default=True, help_text=_("If false,the DNS will not provide this ip.") ) class Meta: @@ -1856,8 +1826,9 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): def check_and_replace_prefix(self, prefix=None): """Check if the IPv6 prefix is correct and update it if not.""" - prefix_v6 = prefix or self.interface.machine_type.ip_type.prefix_v6.encode().decode( - "utf-8" + prefix_v6 = ( + prefix + or self.interface.machine_type.ip_type.prefix_v6.encode().decode("utf-8") ) if not prefix_v6: return @@ -1930,7 +1901,11 @@ class Domain(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): ) extension = models.ForeignKey("Extension", on_delete=models.PROTECT) cname = models.ForeignKey( - "self", null=True, blank=True, related_name="related_domain", on_delete=models.PROTECT + "self", + null=True, + blank=True, + related_name="related_domain", + on_delete=models.PROTECT, ) ttl = models.PositiveIntegerField( verbose_name=_("Time To Live (TTL)"), @@ -1940,9 +1915,7 @@ class Domain(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): class Meta: unique_together = (("name", "extension"),) - permissions = ( - ("change_ttl", _("Can change the TTL of a domain object")), - ) + permissions = (("change_ttl", _("Can change the TTL of a domain object")),) verbose_name = _("domain") verbose_name_plural = _("domains") @@ -2037,8 +2010,10 @@ class Domain(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model): except Interface.DoesNotExist: return False, _("Nonexistent interface."), None if not user_request.has_perm("machines.add_domain"): - max_lambdauser_aliases = preferences.models.OptionalMachine.get_cached_value( - "max_lambdauser_aliases" + max_lambdauser_aliases = ( + preferences.models.OptionalMachine.get_cached_value( + "max_lambdauser_aliases" + ) ) if interface.machine.user != user_request: return ( @@ -2173,8 +2148,7 @@ class IpList(RevMixin, AclMixin, models.Model): @cached_property def need_infra(self): - """Check if the 'infra' right is required to assign this IP address. - """ + """Check if the 'infra' right is required to assign this IP address.""" return self.ip_type.need_infra def clean(self): @@ -2206,20 +2180,13 @@ class IpList(RevMixin, AclMixin, models.Model): """ can, _message, _group = IpType.can_use_all(user_request) if can: - return ( - True, - None, - None, - cls.objects.all() - ) + return (True, None, None, cls.objects.all()) else: return ( True, _("You don't have the right to use all machine types."), ("machines.use_all_machinetype",), - cls.objects.filter( - ip_type__in=IpType.objects.filter(need_infra=False) - ), + cls.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)), ) def __str__(self): @@ -2517,7 +2484,13 @@ class OuverturePort(RevMixin, AclMixin, models.Model): def machine_post_save(**kwargs): """Synchronise LDAP and regen firewall/DHCP after a machine is edited.""" user = kwargs["instance"].user - users.signals.synchronise.send(sender=users.models.User, instance=user, base=False, access_refresh=False, mac_refresh=True) + users.signals.synchronise.send( + sender=users.models.User, + instance=user, + base=False, + access_refresh=False, + mac_refresh=True, + ) regen("dhcp") regen("mac_ip_list") @@ -2527,7 +2500,13 @@ def machine_post_delete(**kwargs): """Synchronise LDAP and regen firewall/DHCP after a machine is deleted.""" machine = kwargs["instance"] user = machine.user - users.signals.synchronise.send(sender=users.models.User, instance=user, base=False, access_refresh=False, mac_refresh=True) + users.signals.synchronise.send( + sender=users.models.User, + instance=user, + base=False, + access_refresh=False, + mac_refresh=True, + ) regen("dhcp") regen("mac_ip_list") @@ -2540,7 +2519,13 @@ def interface_post_save(**kwargs): interface = kwargs["instance"] interface.sync_ipv6() user = interface.machine.user - users.signals.synchronise.send(sender=users.models.User, instance=user, base=False, access_refresh=False, mac_refresh=True) + users.signals.synchronise.send( + sender=users.models.User, + instance=user, + base=False, + access_refresh=False, + mac_refresh=True, + ) # Regen services regen("dhcp") regen("mac_ip_list") @@ -2552,11 +2537,16 @@ def interface_post_save(**kwargs): @receiver(post_delete, sender=Interface) def interface_post_delete(**kwargs): - """Synchronise LDAP and regen firewall/DHCP after an interface is deleted. - """ + """Synchronise LDAP and regen firewall/DHCP after an interface is deleted.""" interface = kwargs["instance"] user = interface.machine.user - users.signals.synchronise.send(sender=users.models.User, instance=user, base=False, access_refresh=False, mac_refresh=True) + users.signals.synchronise.send( + sender=users.models.User, + instance=user, + base=False, + access_refresh=False, + mac_refresh=True, + ) @receiver(post_save, sender=IpType) diff --git a/machines/urls.py b/machines/urls.py index 94529a76..b8ed184c 100644 --- a/machines/urls.py +++ b/machines/urls.py @@ -28,8 +28,7 @@ from __future__ import unicode_literals from django.urls import path -from . import views -from . import views_autocomplete +from . import views, views_autocomplete app_name = "machines" @@ -96,9 +95,7 @@ urlpatterns = [ path("add_alias/", views.add_alias, name="add-alias"), path("edit_alias/", views.edit_alias, name="edit-alias"), path("del_alias/", views.del_alias, name="del-alias"), - path( - "index_alias/", views.index_alias, name="index-alias" - ), + path("index_alias/", views.index_alias, name="index-alias"), path( "new_ipv6list/", views.new_ipv6list, @@ -116,9 +113,7 @@ urlpatterns = [ ), path("index_ipv6/", views.index_ipv6, name="index-ipv6"), path("add_service", views.add_service, name="add-service"), - path( - "edit_service/", views.edit_service, name="edit-service" - ), + path("edit_service/", views.edit_service, name="edit-service"), path("del_service", views.del_service, name="del-service"), path( "regen_service/", @@ -157,13 +152,49 @@ urlpatterns = [ name="port-config", ), ### Autocomplete Views - path('vlan-autocomplete', views_autocomplete.VlanAutocomplete.as_view(), name='vlan-autocomplete',), - path('interface-autocomplete', views_autocomplete.InterfaceAutocomplete.as_view(), name='interface-autocomplete',), - path('machine-autocomplete', views_autocomplete.MachineAutocomplete.as_view(), name='machine-autocomplete',), - path('machinetype-autocomplete', views_autocomplete.MachineTypeAutocomplete.as_view(), name='machinetype-autocomplete',), - path('iptype-autocomplete', views_autocomplete.IpTypeAutocomplete.as_view(), name='iptype-autocomplete',), - path('extension-autocomplete', views_autocomplete.ExtensionAutocomplete.as_view(), name='extension-autocomplete',), - path('domain-autocomplete', views_autocomplete.DomainAutocomplete.as_view(), name='domain-autocomplete',), - path('ouvertureportlist-autocomplete', views_autocomplete.OuverturePortListAutocomplete.as_view(), name='ouvertureportlist-autocomplete',), - path('iplist-autocomplete', views_autocomplete.IpListAutocomplete.as_view(), name='iplist-autocomplete',), + path( + "vlan-autocomplete", + views_autocomplete.VlanAutocomplete.as_view(), + name="vlan-autocomplete", + ), + path( + "interface-autocomplete", + views_autocomplete.InterfaceAutocomplete.as_view(), + name="interface-autocomplete", + ), + path( + "machine-autocomplete", + views_autocomplete.MachineAutocomplete.as_view(), + name="machine-autocomplete", + ), + path( + "machinetype-autocomplete", + views_autocomplete.MachineTypeAutocomplete.as_view(), + name="machinetype-autocomplete", + ), + path( + "iptype-autocomplete", + views_autocomplete.IpTypeAutocomplete.as_view(), + name="iptype-autocomplete", + ), + path( + "extension-autocomplete", + views_autocomplete.ExtensionAutocomplete.as_view(), + name="extension-autocomplete", + ), + path( + "domain-autocomplete", + views_autocomplete.DomainAutocomplete.as_view(), + name="domain-autocomplete", + ), + path( + "ouvertureportlist-autocomplete", + views_autocomplete.OuverturePortListAutocomplete.as_view(), + name="ouvertureportlist-autocomplete", + ), + path( + "iplist-autocomplete", + views_autocomplete.IpListAutocomplete.as_view(), + name="iplist-autocomplete", + ), ] diff --git a/machines/views.py b/machines/views.py index 04b8a3a8..461862cf 100644 --- a/machines/views.py +++ b/machines/views.py @@ -34,11 +34,11 @@ from __future__ import unicode_literals from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required -from django.db.models import ProtectedError, F from django.db import IntegrityError +from django.db.models import F, ProtectedError from django.forms import modelformset_factory from django.http import HttpResponse -from django.shortcuts import render, redirect +from django.shortcuts import redirect, render from django.template.loader import render_to_string from django.urls import reverse from django.utils.translation import ugettext as _ @@ -46,82 +46,28 @@ from django.views.decorators.csrf import csrf_exempt from rest_framework.renderers import JSONRenderer from preferences.models import GeneralOption -from re2o.acl import ( - can_create, - can_edit, - can_view, - can_delete, - can_view_all, - can_delete_set, -) -from re2o.utils import all_active_assigned_interfaces, filter_active_interfaces +from re2o.acl import (can_create, can_delete, can_delete_set, can_edit, + can_view, can_view_all) from re2o.base import SortTable, re2o_paginator +from re2o.utils import all_active_assigned_interfaces, filter_active_interfaces from re2o.views import form from users.models import User -from .forms import ( - NewMachineForm, - EditMachineForm, - EditInterfaceForm, - AddInterfaceForm, - MachineTypeForm, - DelMachineTypeForm, - ExtensionForm, - DelExtensionForm, - EditIpTypeForm, - IpTypeForm, - DelIpTypeForm, - DomainForm, - AliasForm, - DelAliasForm, - SOAForm, - DelSOAForm, - NsForm, - DelNsForm, - TxtForm, - DelTxtForm, - DNameForm, - DelDNameForm, - MxForm, - DelMxForm, - VlanForm, - DelVlanForm, - RoleForm, - DelRoleForm, - ServiceForm, - DelServiceForm, - SshFpForm, - NasForm, - DelNasForm, - SrvForm, - DelSrvForm, - Ipv6ListForm, - EditOuverturePortListForm, - EditOuverturePortConfigForm, -) -from .models import ( - IpType, - Machine, - Interface, - MachineType, - Extension, - SOA, - Mx, - Ns, - Domain, - Role, - Service, - Service_link, - regen, - Vlan, - Nas, - Txt, - DName, - Srv, - SshFp, - OuverturePortList, - OuverturePort, - Ipv6List, -) + +from .forms import (AddInterfaceForm, AliasForm, DelAliasForm, DelDNameForm, + DelExtensionForm, DelIpTypeForm, DelMachineTypeForm, + DelMxForm, DelNasForm, DelNsForm, DelRoleForm, + DelServiceForm, DelSOAForm, DelSrvForm, DelTxtForm, + DelVlanForm, DNameForm, DomainForm, EditInterfaceForm, + EditIpTypeForm, EditMachineForm, + EditOuverturePortConfigForm, EditOuverturePortListForm, + ExtensionForm, IpTypeForm, Ipv6ListForm, MachineTypeForm, + MxForm, NasForm, NewMachineForm, NsForm, RoleForm, + ServiceForm, SOAForm, SrvForm, SshFpForm, TxtForm, + VlanForm) +from .models import (SOA, DName, Domain, Extension, Interface, IpType, + Ipv6List, Machine, MachineType, Mx, Nas, Ns, + OuverturePort, OuverturePortList, Role, Service, + Service_link, Srv, SshFp, Txt, Vlan, regen) @login_required @@ -135,7 +81,9 @@ def new_machine(request, user, **_kwargs): """ machine = NewMachineForm(request.POST or None, user=request.user) interface = AddInterfaceForm(request.POST or None, user=request.user) - domain = DomainForm(request.POST or None, user=user, initial={'name': user.get_next_domain_name()}) + domain = DomainForm( + request.POST or None, user=user, initial={"name": user.get_next_domain_name()} + ) if machine.is_valid() and interface.is_valid(): new_machine_obj = machine.save(commit=False) new_machine_obj.user = user @@ -229,7 +177,11 @@ def new_interface(request, machine, **_kwargs): machine. """ interface_form = AddInterfaceForm(request.POST or None, user=request.user) - domain_form = DomainForm(request.POST or None, user=request.user, initial={'name': machine.user.get_next_domain_name()}) + domain_form = DomainForm( + request.POST or None, + user=request.user, + initial={"name": machine.user.get_next_domain_name()}, + ) if interface_form.is_valid(): new_interface_obj = interface_form.save(commit=False) domain_form.instance.interface_parent = new_interface_obj @@ -268,7 +220,9 @@ def del_interface(request, interface, **_kwargs): reverse("users:profil", kwargs={"userid": str(request.user.id)}) ) return form( - {"objet": interface, "objet_name": _("interface")}, "machines/delete.html", request + {"objet": interface, "objet_name": _("interface")}, + "machines/delete.html", + request, ) @@ -328,7 +282,9 @@ def del_ipv6list(request, ipv6list, **_kwargs): reverse("machines:index-ipv6", kwargs={"interfaceid": str(interfaceid)}) ) return form( - {"objet": ipv6list, "objet_name": _("IPv6 addresses list")}, "machines/delete.html", request + {"objet": ipv6list, "objet_name": _("IPv6 addresses list")}, + "machines/delete.html", + request, ) @@ -384,7 +340,9 @@ def del_sshfp(request, sshfp, **_kwargs): reverse("machines:index-sshfp", kwargs={"machineid": str(machineid)}) ) return form( - {"objet": sshfp, "objet_name": _("SSHFP record")}, "machines/delete.html", request + {"objet": sshfp, "objet_name": _("SSHFP record")}, + "machines/delete.html", + request, ) @@ -421,7 +379,9 @@ def edit_iptype(request, iptype_instance, **_kwargs): iptype.save() messages.success(request, _("The IP type was edited.")) except IntegrityError as e: - messages.success(request, _("This IP type change would create duplicated domains")) + messages.success( + request, _("This IP type change would create duplicated domains") + ) return redirect(reverse("machines:index-iptype")) return form( {"iptypeform": iptype, "action_name": _("Edit")}, @@ -490,7 +450,10 @@ def edit_machinetype(request, machinetype_instance, **_kwargs): machinetype.save() messages.success(request, _("The machine type was edited.")) except IntegrityError as e: - messages.error(request, _("This machine type change would create duplicated domains")) + messages.error( + request, + _("This machine type change would create duplicated domains"), + ) return redirect(reverse("machines:index-machinetype")) return form( {"machinetypeform": machinetype, "action_name": _("Edit")}, @@ -580,10 +543,16 @@ def del_extension(request, instances): _( "The extension %s is assigned to following %s : %s" ", you can't delete it." - ) % ( + ) + % ( extension_del, str(e.protected_objects.model._meta.verbose_name_plural), - ",".join(map(lambda x: str(x['name']), e.protected_objects.values('name').iterator())) + ",".join( + map( + lambda x: str(x["name"]), + e.protected_objects.values("name").iterator(), + ) + ), ) ), ) @@ -1221,17 +1190,18 @@ def index(request): machines_list = re2o_paginator(request, machines_list, pagination_large_number) return render(request, "machines/index.html", {"machines_list": machines_list}) + # Canonic view for displaying machines in users's profil def aff_profil(request, user): """View used to display the machines on a user's profile.""" machines = ( - Machine.objects.filter(user=user) + Machine.objects.filter(user=user) .select_related("user") .prefetch_related("interface_set__domain__extension") .prefetch_related("interface_set__ipv4__ip_type__extension") .prefetch_related("interface_set__machine_type") .prefetch_related("interface_set__domain__related_domain__extension") - ) + ) machines = SortTable.sort( machines, request.GET.get("col"), @@ -1243,19 +1213,16 @@ def aff_profil(request, user): machines = re2o_paginator(request, machines, pagination_large_number) context = { - "users":user, - "machines_list": machines, - "nb_machines":nb_machines, + "users": user, + "machines_list": machines, + "nb_machines": nb_machines, } return render_to_string( - "machines/aff_profil.html",context=context,request=request,using=None + "machines/aff_profil.html", context=context, request=request, using=None ) - - - @login_required @can_view_all(IpType) def index_iptype(request): @@ -1531,4 +1498,3 @@ def configure_ports(request, interface_instance, **_kwargs): "machines/machine.html", request, ) - diff --git a/machines/views_autocomplete.py b/machines/views_autocomplete.py index 94b55a59..51010f9a 100644 --- a/machines/views_autocomplete.py +++ b/machines/views_autocomplete.py @@ -31,23 +31,14 @@ Here are defined the autocomplete class based view. """ from __future__ import unicode_literals -from django.db.models import Q, Value, CharField +from django.db.models import CharField, Q, Value from django.db.models.functions import Concat -from .models import ( - Interface, - Machine, - Vlan, - MachineType, - IpType, - Extension, - Domain, - OuverturePortList, - IpList, -) - from re2o.views import AutocompleteViewMixin +from .models import (Domain, Extension, Interface, IpList, IpType, Machine, + MachineType, OuverturePortList, Vlan) + class VlanAutocomplete(AutocompleteViewMixin): obj_type = Vlan diff --git a/multi_op/forms.py b/multi_op/forms.py index f4c839de..a953679f 100644 --- a/multi_op/forms.py +++ b/multi_op/forms.py @@ -27,11 +27,11 @@ Select a dorm from django import forms -from django.forms import ModelForm, Form -from re2o.field_permissions import FieldPermissionFormMixin -from re2o.mixins import FormRevMixin +from django.forms import Form, ModelForm from django.utils.translation import ugettext_lazy as _ +from re2o.field_permissions import FieldPermissionFormMixin +from re2o.mixins import FormRevMixin from topologie.models import Dormitory from .preferences.models import MultiopOption @@ -49,4 +49,6 @@ class DormitoryForm(FormRevMixin, Form): def __init__(self, *args, **kwargs): super(DormitoryForm, self).__init__(*args, **kwargs) - self.fields["dormitory"].queryset = MultiopOption.get_cached_value("enabled_dorm").all() + self.fields["dormitory"].queryset = MultiopOption.get_cached_value( + "enabled_dorm" + ).all() diff --git a/multi_op/models.py b/multi_op/models.py index 4b74df49..e9fdca24 100644 --- a/multi_op/models.py +++ b/multi_op/models.py @@ -25,21 +25,18 @@ Multi_op model from __future__ import absolute_import +from django.core.mail import EmailMessage from django.db import models -from django.utils.translation import ugettext_lazy as _ -from django.template import loader from django.db.models.signals import post_save from django.dispatch import receiver +from django.template import loader from django.utils.functional import cached_property - +from django.utils.translation import ugettext_lazy as _ from reversion.models import Version -from re2o.mixins import AclMixin -from re2o.mail_utils import send_mail_object -from django.core.mail import EmailMessage - -from preferences.models import GeneralOption - import users.models +from preferences.models import GeneralOption +from re2o.mail_utils import send_mail_object +from re2o.mixins import AclMixin from .preferences.models import MultiopOption diff --git a/multi_op/preferences/__init__.py b/multi_op/preferences/__init__.py index 737e0f26..bbe53822 100644 --- a/multi_op/preferences/__init__.py +++ b/multi_op/preferences/__init__.py @@ -4,7 +4,7 @@ # quelques clics. # # Copyright © 2020 Gabriel Détraz -# Copyright © 2019 Arthur Grisel-Davy +# Copyright © 2019 Arthur Grisel-Davy # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/multi_op/preferences/forms.py b/multi_op/preferences/forms.py index 45941007..d53a212c 100644 --- a/multi_op/preferences/forms.py +++ b/multi_op/preferences/forms.py @@ -27,8 +27,9 @@ each. from django import forms -from django.forms import ModelForm, Form +from django.forms import Form, ModelForm from django.utils.translation import ugettext_lazy as _ + from re2o.widgets import AutocompleteMultipleModelWidget from .models import MultiopOption diff --git a/multi_op/preferences/models.py b/multi_op/preferences/models.py index d4228e8e..2a9c9e40 100644 --- a/multi_op/preferences/models.py +++ b/multi_op/preferences/models.py @@ -27,8 +27,8 @@ with multiple operators. from django.db import models from django.utils.translation import ugettext_lazy as _ -from re2o.mixins import AclMixin, RevMixin from preferences.models import PreferencesModel +from re2o.mixins import AclMixin, RevMixin class MultiopOption(AclMixin, PreferencesModel): diff --git a/multi_op/preferences/views.py b/multi_op/preferences/views.py index 87e34e6d..1fd3f962 100644 --- a/multi_op/preferences/views.py +++ b/multi_op/preferences/views.py @@ -26,20 +26,16 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.shortcuts import render, redirect +from django.shortcuts import redirect, render from django.template.loader import render_to_string -from django.utils.translation import ugettext as _ from django.urls import reverse - -from re2o.base import re2o_paginator - -from re2o.acl import can_view, can_view_all, can_edit, can_create +from django.utils.translation import ugettext as _ from preferences.views import edit_options_template_function +from re2o.acl import can_create, can_edit, can_view, can_view_all +from re2o.base import re2o_paginator -from . import forms -from . import models - +from . import forms, models def aff_preferences(request): diff --git a/multi_op/views.py b/multi_op/views.py index b4a559f4..e4650d92 100644 --- a/multi_op/views.py +++ b/multi_op/views.py @@ -27,28 +27,24 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.shortcuts import render, redirect -from django.template.loader import render_to_string -from django.views.decorators.cache import cache_page -from django.utils.translation import ugettext as _ -from django.urls import reverse -from django.forms import modelformset_factory from django.db.models import Q +from django.forms import modelformset_factory +from django.shortcuts import redirect, render +from django.template.loader import render_to_string +from django.urls import reverse +from django.utils.translation import ugettext as _ +from django.views.decorators.cache import cache_page + +from preferences.models import AssoOption, GeneralOption +from re2o.acl import can_create, can_edit, can_view, can_view_all +from re2o.base import SortTable, re2o_paginator +from re2o.utils import all_adherent, all_has_access from re2o.views import form -from re2o.utils import all_has_access, all_adherent - -from re2o.base import re2o_paginator, SortTable - -from re2o.acl import can_view, can_view_all, can_edit, can_create - -from preferences.models import GeneralOption, AssoOption +from topologie.models import Dormitory, Room from .forms import DormitoryForm - from .preferences.models import MultiopOption -from topologie.models import Room, Dormitory - def display_rooms_connection(request, dormitory=None): """View used to display an overview of the rooms' connection state. @@ -58,9 +54,13 @@ def display_rooms_connection(request, dormitory=None): dormitory: Dormitory, the dormitory used to filter rooms. If no dormitory is given, all rooms are displayed (default: None). """ - room_list = Room.objects.select_related("building__dormitory").filter( - building__dormitory__in=MultiopOption.get_cached_value("enabled_dorm").all() - ).order_by("building_dormitory", "port") + room_list = ( + Room.objects.select_related("building__dormitory") + .filter( + building__dormitory__in=MultiopOption.get_cached_value("enabled_dorm").all() + ) + .order_by("building_dormitory", "port") + ) if dormitory: room_list = room_list.filter(building__dormitory=dormitory) room_list = SortTable.sort( @@ -113,7 +113,9 @@ def aff_pending_connection(request): Room.objects.select_related("building__dormitory") .filter(port__isnull=True) .filter(adherent__in=all_has_access()) - .filter(building__dormitory__in=MultiopOption.get_cached_value("enabled_dorm").all()) + .filter( + building__dormitory__in=MultiopOption.get_cached_value("enabled_dorm").all() + ) .order_by("building_dormitory", "port") ) dormitory_form = DormitoryForm(request.POST or None) @@ -151,7 +153,9 @@ def aff_pending_disconnection(request): Room.objects.select_related("building__dormitory") .filter(port__isnull=False) .exclude(Q(adherent__in=all_has_access()) | Q(adherent__in=all_adherent())) - .filter(building__dormitory__in=MultiopOption.get_cached_value("enabled_dorm").all()) + .filter( + building__dormitory__in=MultiopOption.get_cached_value("enabled_dorm").all() + ) .order_by("building_dormitory", "port") ) dormitory_form = DormitoryForm(request.POST or None) diff --git a/preferences/admin.py b/preferences/admin.py index fc55d93d..c88016cc 100644 --- a/preferences/admin.py +++ b/preferences/admin.py @@ -28,21 +28,10 @@ from __future__ import unicode_literals from django.contrib import admin from reversion.admin import VersionAdmin -from .models import ( - OptionalUser, - OptionalMachine, - OptionalTopologie, - GeneralOption, - Service, - MailContact, - AssoOption, - MailMessageOption, - HomeOption, - RadiusKey, - SwitchManagementCred, - Reminder, - DocumentTemplate, -) +from .models import (AssoOption, DocumentTemplate, GeneralOption, HomeOption, + MailContact, MailMessageOption, OptionalMachine, + OptionalTopologie, OptionalUser, RadiusKey, Reminder, + Service, SwitchManagementCred) class OptionalUserAdmin(VersionAdmin): diff --git a/preferences/api/serializers.py b/preferences/api/serializers.py index 47a4a07a..a0b757c3 100644 --- a/preferences/api/serializers.py +++ b/preferences/api/serializers.py @@ -22,11 +22,12 @@ from rest_framework import serializers import preferences.models as preferences -from api.serializers import NamespacedHRField, NamespacedHIField, NamespacedHMSerializer +from api.serializers import (NamespacedHIField, NamespacedHMSerializer, + NamespacedHRField) + class OptionalUserSerializer(NamespacedHMSerializer): - """Serialize `preferences.models.OptionalUser` objects. - """ + """Serialize `preferences.models.OptionalUser` objects.""" tel_mandatory = serializers.BooleanField(source="is_tel_mandatory") shell_default = serializers.StringRelatedField() @@ -47,8 +48,7 @@ class OptionalUserSerializer(NamespacedHMSerializer): class OptionalMachineSerializer(NamespacedHMSerializer): - """Serialize `preferences.models.OptionalMachine` objects. - """ + """Serialize `preferences.models.OptionalMachine` objects.""" class Meta: model = preferences.OptionalMachine @@ -59,13 +59,12 @@ class OptionalMachineSerializer(NamespacedHMSerializer): "ipv6_mode", "create_machine", "ipv6", - "default_dns_ttl" + "default_dns_ttl", ) class OptionalTopologieSerializer(NamespacedHMSerializer): - """Serialize `preferences.models.OptionalTopologie` objects. - """ + """Serialize `preferences.models.OptionalTopologie` objects.""" switchs_management_interface_ip = serializers.CharField() @@ -85,8 +84,7 @@ class OptionalTopologieSerializer(NamespacedHMSerializer): class RadiusOptionSerializer(NamespacedHMSerializer): - """Serialize `preferences.models.RadiusOption` objects - """ + """Serialize `preferences.models.RadiusOption` objects""" class Meta: model = preferences.RadiusOption @@ -107,8 +105,7 @@ class RadiusOptionSerializer(NamespacedHMSerializer): class GeneralOptionSerializer(NamespacedHMSerializer): - """Serialize `preferences.models.GeneralOption` objects. - """ + """Serialize `preferences.models.GeneralOption` objects.""" class Meta: model = preferences.GeneralOption @@ -128,8 +125,7 @@ class GeneralOptionSerializer(NamespacedHMSerializer): class HomeServiceSerializer(NamespacedHMSerializer): - """Serialize `preferences.models.Service` objects. - """ + """Serialize `preferences.models.Service` objects.""" class Meta: model = preferences.Service @@ -138,8 +134,7 @@ class HomeServiceSerializer(NamespacedHMSerializer): class AssoOptionSerializer(NamespacedHMSerializer): - """Serialize `preferences.models.AssoOption` objects. - """ + """Serialize `preferences.models.AssoOption` objects.""" class Meta: model = preferences.AssoOption @@ -157,8 +152,7 @@ class AssoOptionSerializer(NamespacedHMSerializer): class HomeOptionSerializer(NamespacedHMSerializer): - """Serialize `preferences.models.HomeOption` objects. - """ + """Serialize `preferences.models.HomeOption` objects.""" class Meta: model = preferences.HomeOption @@ -166,8 +160,7 @@ class HomeOptionSerializer(NamespacedHMSerializer): class MailMessageOptionSerializer(NamespacedHMSerializer): - """Serialize `preferences.models.MailMessageOption` objects. - """ + """Serialize `preferences.models.MailMessageOption` objects.""" class Meta: model = preferences.MailMessageOption diff --git a/preferences/api/urls.py b/preferences/api/urls.py index df0a210c..87ffa74e 100644 --- a/preferences/api/urls.py +++ b/preferences/api/urls.py @@ -21,9 +21,7 @@ from . import views -urls_viewset = [ - (r"preferences/service", views.HomeServiceViewSet, "homeservice") -] +urls_viewset = [(r"preferences/service", views.HomeServiceViewSet, "homeservice")] urls_view = [ (r"preferences/optionaluser", views.OptionalUserView), @@ -33,5 +31,5 @@ urls_view = [ (r"preferences/generaloption", views.GeneralOptionView), (r"preferences/assooption", views.AssoOptionView), (r"preferences/homeoption", views.HomeOptionView), - (r"preferences/mailmessageoption", views.MailMessageOptionView) -] \ No newline at end of file + (r"preferences/mailmessageoption", views.MailMessageOptionView), +] diff --git a/preferences/api/views.py b/preferences/api/views.py index e52766f1..821a7ea2 100644 --- a/preferences/api/views.py +++ b/preferences/api/views.py @@ -19,16 +19,16 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from rest_framework import viewsets, generics +from rest_framework import generics, viewsets -from . import serializers import preferences.models as preferences from api.permissions import ACLPermission +from . import serializers + class OptionalUserView(generics.RetrieveAPIView): - """Exposes details of `preferences.models.` settings. - """ + """Exposes details of `preferences.models.` settings.""" permission_classes = (ACLPermission,) perms_map = {"GET": [preferences.OptionalUser.can_view_all]} @@ -39,8 +39,7 @@ class OptionalUserView(generics.RetrieveAPIView): class OptionalMachineView(generics.RetrieveAPIView): - """Exposes details of `preferences.models.OptionalMachine` settings. - """ + """Exposes details of `preferences.models.OptionalMachine` settings.""" permission_classes = (ACLPermission,) perms_map = {"GET": [preferences.OptionalMachine.can_view_all]} @@ -51,8 +50,7 @@ class OptionalMachineView(generics.RetrieveAPIView): class OptionalTopologieView(generics.RetrieveAPIView): - """Exposes details of `preferences.models.OptionalTopologie` settings. - """ + """Exposes details of `preferences.models.OptionalTopologie` settings.""" permission_classes = (ACLPermission,) perms_map = {"GET": [preferences.OptionalTopologie.can_view_all]} @@ -63,8 +61,7 @@ class OptionalTopologieView(generics.RetrieveAPIView): class RadiusOptionView(generics.RetrieveAPIView): - """Exposes details of `preferences.models.OptionalTopologie` settings. - """ + """Exposes details of `preferences.models.OptionalTopologie` settings.""" permission_classes = (ACLPermission,) perms_map = {"GET": [preferences.RadiusOption.can_view_all]} @@ -75,8 +72,7 @@ class RadiusOptionView(generics.RetrieveAPIView): class GeneralOptionView(generics.RetrieveAPIView): - """Exposes details of `preferences.models.GeneralOption` settings. - """ + """Exposes details of `preferences.models.GeneralOption` settings.""" permission_classes = (ACLPermission,) perms_map = {"GET": [preferences.GeneralOption.can_view_all]} @@ -87,16 +83,14 @@ class GeneralOptionView(generics.RetrieveAPIView): class HomeServiceViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `preferences.models.Service` objects. - """ + """Exposes list and details of `preferences.models.Service` objects.""" queryset = preferences.Service.objects.all() serializer_class = serializers.HomeServiceSerializer class AssoOptionView(generics.RetrieveAPIView): - """Exposes details of `preferences.models.AssoOption` settings. - """ + """Exposes details of `preferences.models.AssoOption` settings.""" permission_classes = (ACLPermission,) perms_map = {"GET": [preferences.AssoOption.can_view_all]} @@ -107,8 +101,7 @@ class AssoOptionView(generics.RetrieveAPIView): class HomeOptionView(generics.RetrieveAPIView): - """Exposes details of `preferences.models.HomeOption` settings. - """ + """Exposes details of `preferences.models.HomeOption` settings.""" permission_classes = (ACLPermission,) perms_map = {"GET": [preferences.HomeOption.can_view_all]} @@ -119,12 +112,11 @@ class HomeOptionView(generics.RetrieveAPIView): class MailMessageOptionView(generics.RetrieveAPIView): - """Exposes details of `preferences.models.MailMessageOption` settings. - """ + """Exposes details of `preferences.models.MailMessageOption` settings.""" permission_classes = (ACLPermission,) perms_map = {"GET": [preferences.MailMessageOption.can_view_all]} serializer_class = serializers.MailMessageOptionSerializer def get_object(self): - return preferences.MailMessageOption.objects.first() \ No newline at end of file + return preferences.MailMessageOption.objects.first() diff --git a/preferences/apps.py b/preferences/apps.py index 2db4b4b1..729da0af 100644 --- a/preferences/apps.py +++ b/preferences/apps.py @@ -8,4 +8,4 @@ from django.apps import AppConfig class PreferencesConfig(AppConfig): """Configuration of preferences app.""" - name = "preferences" \ No newline at end of file + name = "preferences" diff --git a/preferences/forms.py b/preferences/forms.py index e19737c1..032290e4 100644 --- a/preferences/forms.py +++ b/preferences/forms.py @@ -25,36 +25,22 @@ Forms to edit preferences: users, machines, topology, organisation etc. from __future__ import unicode_literals -from django.forms import ModelForm, Form -from django.db.models import Q from django import forms +from django.db.models import Q +from django.forms import Form, ModelForm from django.utils.translation import ugettext_lazy as _ + from re2o.mixins import FormRevMixin -from re2o.widgets import ( - AutocompleteModelWidget, - AutocompleteMultipleModelWidget -) -from .models import ( - OptionalUser, - OptionalMachine, - OptionalTopologie, - GeneralOption, - AssoOption, - MailMessageOption, - HomeOption, - Service, - MailContact, - Reminder, - RadiusKey, - SwitchManagementCred, - RadiusOption, - CotisationsOption, - DocumentTemplate, - RadiusAttribute, - Mandate, -) +from re2o.widgets import (AutocompleteModelWidget, + AutocompleteMultipleModelWidget) from topologie.models import Switch +from .models import (AssoOption, CotisationsOption, DocumentTemplate, + GeneralOption, HomeOption, MailContact, MailMessageOption, + Mandate, OptionalMachine, OptionalTopologie, OptionalUser, + RadiusAttribute, RadiusKey, RadiusOption, Reminder, + Service, SwitchManagementCred) + class EditOptionalUserForm(ModelForm): """Form used to edit user preferences.""" @@ -74,14 +60,22 @@ class EditOptionalUserForm(ModelForm): self.fields["self_change_shell"].label = _("Self change shell") self.fields["self_change_pseudo"].label = _("Self change pseudo") self.fields["self_room_policy"].label = _("Self room policy") - self.fields["local_email_accounts_enabled"].label = _("Local email accounts enabled") + self.fields["local_email_accounts_enabled"].label = _( + "Local email accounts enabled" + ) self.fields["local_email_domain"].label = _("Local email domain") self.fields["max_email_address"].label = _("Max local email address") self.fields["delete_notyetactive"].label = _("Delete not yet active users") - self.fields["disable_emailnotyetconfirmed"].label = _("Disabled email not yet confirmed") + self.fields["disable_emailnotyetconfirmed"].label = _( + "Disabled email not yet confirmed" + ) self.fields["self_adhesion"].label = _("Self registration") - self.fields["all_users_active"].label = _("All users are state active by default") - self.fields["allow_set_password_during_user_creation"].label = _("Allow set password during user creation") + self.fields["all_users_active"].label = _( + "All users are state active by default" + ) + self.fields["allow_set_password_during_user_creation"].label = _( + "Allow set password during user creation" + ) self.fields["allow_archived_connexion"].label = _("Allow archived connexion") @@ -211,9 +205,7 @@ class EditMailMessageOptionForm(ModelForm): def __init__(self, *args, **kwargs): prefix = kwargs.pop("prefix", self.Meta.model.__name__) super(EditMailMessageOptionForm, self).__init__(*args, prefix=prefix, **kwargs) - self.fields["welcome_mail_fr"].label = _( - "Message for the French welcome email" - ) + self.fields["welcome_mail_fr"].label = _("Message for the French welcome email") self.fields["welcome_mail_en"].label = _( "Message for the English welcome email" ) @@ -355,8 +347,7 @@ class ServiceForm(ModelForm): class DelServiceForm(Form): - """Form used to delete one or several services displayed on the home page. - """ + """Form used to delete one or several services displayed on the home page.""" services = forms.ModelMultipleChoiceField( queryset=Service.objects.none(), diff --git a/preferences/models.py b/preferences/models.py index b35acac3..799fd006 100644 --- a/preferences/models.py +++ b/preferences/models.py @@ -25,23 +25,22 @@ Models defining the preferences for users, machines, emails, general settings etc. """ from __future__ import unicode_literals -import os -from django.utils.functional import cached_property -from django.utils import timezone +import os +from datetime import timedelta + +from django.core.cache import cache from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver -from django.core.cache import cache from django.forms import ValidationError +from django.utils import timezone +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ import machines.models - -from re2o.mixins import AclMixin, RevMixin from re2o.aes_field import AESEncryptedField - -from datetime import timedelta +from re2o.mixins import AclMixin, RevMixin class PreferencesModel(models.Model): @@ -328,7 +327,7 @@ class OptionalTopologie(AclMixin, PreferencesModel): configuration. """ if self.switchs_ip_type: - from machines.models import Role, Interface + from machines.models import Interface, Role return ( Interface.objects.filter( @@ -364,14 +363,14 @@ class OptionalTopologie(AclMixin, PreferencesModel): """Get the dictionary of IP addresses for the configuration of switches. """ - from machines.models import Role, Ipv6List, Interface + from machines.models import Interface, Ipv6List, Role def return_ips_dict(interfaces): return { "ipv4": [str(interface.ipv4) for interface in interfaces], - "ipv6": Ipv6List.objects.filter(interface__in=interfaces).filter(active=True).values_list( - "ipv6", flat=True - ), + "ipv6": Ipv6List.objects.filter(interface__in=interfaces) + .filter(active=True) + .values_list("ipv6", flat=True), } ntp_servers = Role.all_interfaces_for_roletype("ntp-server").filter( @@ -660,7 +659,7 @@ class Mandate(RevMixin, AclMixin, models.Model): @classmethod def get_mandate(cls, date=timezone.now): - """"Get the mandate taking place at the given date. + """ "Get the mandate taking place at the given date. Args: date: the date used to find the mandate (default: timezone.now). diff --git a/preferences/urls.py b/preferences/urls.py index b5b72599..96a9b231 100644 --- a/preferences/urls.py +++ b/preferences/urls.py @@ -79,9 +79,7 @@ urlpatterns = [ name="edit-options", ), path("add_service", views.add_service, name="add-service"), - path( - "edit_service/", views.edit_service, name="edit-service" - ), + path("edit_service/", views.edit_service, name="edit-service"), path("del_service/", views.del_service, name="del-service"), path("add_mailcontact", views.add_mailcontact, name="add-mailcontact"), path( @@ -143,13 +141,9 @@ urlpatterns = [ name="del-document-template", ), path("add_mandate", views.add_mandate, name="add-mandate"), - path( - "edit_mandate/", views.edit_mandate, name="edit-mandate" - ), + path("edit_mandate/", views.edit_mandate, name="edit-mandate"), path("del_mandate/", views.del_mandate, name="del-mandate"), - path( - "add_radiusattribute", views.add_radiusattribute, name="add-radiusattribute" - ), + path("add_radiusattribute", views.add_radiusattribute, name="add-radiusattribute"), path( "edit_radiusattribute/", views.edit_radiusattribute, diff --git a/preferences/views.py b/preferences/views.py index 5eab39f4..36b8a5f5 100644 --- a/preferences/views.py +++ b/preferences/views.py @@ -30,61 +30,33 @@ services etc.) from __future__ import unicode_literals -from django.urls import reverse -from django.shortcuts import redirect +from importlib import import_module + from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.db.models import ProtectedError from django.db import transaction +from django.db.models import ProtectedError +from django.shortcuts import redirect +from django.urls import reverse from django.utils.translation import ugettext as _ - from reversion import revisions as reversion -from importlib import import_module +from re2o.acl import (acl_error_message, can_create, can_delete, + can_delete_set, can_edit, can_view_all) from re2o.settings_local import OPTIONNAL_APPS_RE2O from re2o.views import form -from re2o.acl import ( - can_create, - can_edit, - can_delete_set, - can_view_all, - can_delete, - acl_error_message, -) -from .forms import MailContactForm, DelMailContactForm -from .forms import ( - ServiceForm, - ReminderForm, - RadiusKeyForm, - SwitchManagementCredForm, - DocumentTemplateForm, - DelDocumentTemplateForm, - RadiusAttributeForm, - DelRadiusAttributeForm, - MandateForm, -) -from .models import ( - Service, - MailContact, - OptionalUser, - OptionalMachine, - AssoOption, - MailMessageOption, - GeneralOption, - OptionalTopologie, - HomeOption, - Reminder, - RadiusKey, - SwitchManagementCred, - RadiusOption, - CotisationsOption, - DocumentTemplate, - RadiusAttribute, - Mandate, -) -from . import models -from . import forms +from . import forms, models +from .forms import (DelDocumentTemplateForm, DelMailContactForm, + DelRadiusAttributeForm, DocumentTemplateForm, + MailContactForm, MandateForm, RadiusAttributeForm, + RadiusKeyForm, ReminderForm, ServiceForm, + SwitchManagementCredForm) +from .models import (AssoOption, CotisationsOption, DocumentTemplate, + GeneralOption, HomeOption, MailContact, MailMessageOption, + Mandate, OptionalMachine, OptionalTopologie, OptionalUser, + RadiusAttribute, RadiusKey, RadiusOption, Reminder, + Service, SwitchManagementCred) def edit_options_template_function(request, section, forms, models): diff --git a/re2o/acl.py b/re2o/acl.py index 692df905..d1d2c3a9 100644 --- a/re2o/acl.py +++ b/re2o/acl.py @@ -30,8 +30,8 @@ from __future__ import unicode_literals import sys from itertools import chain -from django.db.models import Model from django.contrib import messages +from django.db.models import Model from django.shortcuts import redirect from django.urls import reverse from django.utils.translation import ugettext as _ diff --git a/re2o/aes_field.py b/re2o/aes_field.py index 156531f7..3c847c69 100644 --- a/re2o/aes_field.py +++ b/re2o/aes_field.py @@ -28,22 +28,22 @@ Module defining a AESEncryptedField object that can be used in forms to handle the use of properly encrypting and decrypting AES keys """ -import string import binascii +import string from random import choice -from Crypto.Cipher import AES -from django.db import models +from Crypto.Cipher import AES from django import forms from django.conf import settings +from django.db import models EOD_asbyte = b"`%EofD%`" # This should be something that will not occur in strings EOD = EOD_asbyte.decode("utf-8") def genstring(length=16, chars=string.printable): - """ Generate a random string of length `length` and composed of - the characters in `chars` """ + """Generate a random string of length `length` and composed of + the characters in `chars`""" return "".join([choice(chars) for i in range(length)]) @@ -71,8 +71,8 @@ class AESEncryptedFormField(forms.CharField): class AESEncryptedField(models.CharField): - """ A Field that can be used in forms for adding the support - of AES ecnrypted fields """ + """A Field that can be used in forms for adding the support + of AES ecnrypted fields""" def save_form_data(self, instance, data): setattr( diff --git a/re2o/apps.py b/re2o/apps.py index e19abee2..abcecb77 100644 --- a/re2o/apps.py +++ b/re2o/apps.py @@ -8,4 +8,4 @@ from django.apps import AppConfig class Re2oConfig(AppConfig): """Configuration of re2o app.""" - name = "re2o" \ No newline at end of file + name = "re2o" diff --git a/re2o/base.py b/re2o/base.py index 80f033d8..5c3ac781 100644 --- a/re2o/base.py +++ b/re2o/base.py @@ -26,12 +26,11 @@ Global independant usefull functions import smtplib +from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.utils.translation import ugettext_lazy as _ -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from re2o.settings import EMAIL_HOST - # Mapping of srtftime format for better understanding # https://docs.python.org/3.6/library/datetime.html#strftime-strptime-behavior datetime_mapping = { @@ -64,7 +63,7 @@ datetime_mapping = { def smtp_check(local_part): """Return True if the local_part is already taken - False if available""" + False if available""" try: srv = smtplib.SMTP(EMAIL_HOST) srv.putcmd("vrfy", local_part) @@ -108,9 +107,9 @@ def get_input_formats_help_text(input_formats): class SortTable: - """ Class gathering uselful stuff to sort the colums of a table, according + """Class gathering uselful stuff to sort the colums of a table, according to the column and order requested. It's used with a dict of possible - values and associated model_fields """ + values and associated model_fields""" # All the possible possible values # The naming convention is based on the URL or the views function @@ -228,8 +227,8 @@ class SortTable: @staticmethod def sort(request, col, order, values): - """ Check if the given values are possible and add .order_by() and - a .reverse() as specified according to those values """ + """Check if the given values are possible and add .order_by() and + a .reverse() as specified according to those values""" fields = values.get(col, None) if not fields: fields = values.get("default", []) diff --git a/re2o/context_processors.py b/re2o/context_processors.py index 0fca6037..88471819 100644 --- a/re2o/context_processors.py +++ b/re2o/context_processors.py @@ -24,21 +24,22 @@ from __future__ import unicode_literals import datetime +from importlib import import_module from django.contrib import messages from django.contrib.messages import get_messages from django.http import HttpRequest -from preferences.models import GeneralOption, OptionalMachine from django.utils.translation import get_language -from importlib import import_module + +from preferences.models import GeneralOption, OptionalMachine from re2o.settings_local import OPTIONNAL_APPS_RE2O def context_user(request): """Global Context function - Returns: - dict:Containing user's interfaces and himself if logged, else None + Returns: + dict:Containing user's interfaces and himself if logged, else None """ user = request.user @@ -51,7 +52,9 @@ def context_user(request): if global_message not in [msg.message for msg in get_messages(request)]: messages.warning(request, global_message) else: - if global_message not in [msg.message for msg in get_messages(request._request)]: + if global_message not in [ + msg.message for msg in get_messages(request._request) + ]: messages.warning(request._request, global_message) if user.is_authenticated: interfaces = user.user_interfaces() @@ -70,9 +73,9 @@ def context_user(request): def context_optionnal_apps(request): """Context functions. Called to add optionnal apps buttons in navbari - Returns: - dict:Containing optionnal template list of functions for navbar found - in optional apps + Returns: + dict:Containing optionnal template list of functions for navbar found + in optional apps """ optionnal_apps = [import_module(app) for app in OPTIONNAL_APPS_RE2O] diff --git a/re2o/contributors.py b/re2o/contributors.py index cbca94f0..61e16e2b 100644 --- a/re2o/contributors.py +++ b/re2o/contributors.py @@ -4,44 +4,44 @@ A list of the proud contributors to Re2o """ CONTRIBUTORS = [ - 'Gabriel Detraz', - 'Hugo Levy-falk', - 'Maël Kervella', - 'Jean-romain Garnier', - 'Arthur Grisel-davy', - 'Laouen Fernet', - 'Augustin Lemesle', - 'Lara Kermarec', - 'Root `root` Root', - 'Alexandre Iooss', - 'Yoann Piétri', - 'Charlie Jacomme', - 'Corentin Canebier', - 'Bombar Maxime', - 'Guillaume Goessel', - 'Matthieu Michelet', - 'Edpibu', - 'Fardale', - 'Jean-marie Mineau', - 'David Sinquin', - 'Gabriel Le Bouder', - 'Simon Brélivet', - '~anonymised~', - 'Benjamin Graillot', - 'Leïla Bekaddour', - 'Éloi Alain', - 'Pierre Cadart', - 'Antoine Vintache', - 'Thibault De Boutray', - 'Delphine Salvy', - 'Joanne Steiner', - 'Krokmou', - 'B', - 'Daniel Stan', - 'Gwenael Le Hir', - 'Hugo Hervieux', - 'Mikachu', - 'Nymous', - 'Pierre-antoine Comby', - 'Vincent Le Gallic', -] \ No newline at end of file + "Gabriel Detraz", + "Hugo Levy-falk", + "Maël Kervella", + "Jean-romain Garnier", + "Arthur Grisel-davy", + "Laouen Fernet", + "Augustin Lemesle", + "Lara Kermarec", + "Root `root` Root", + "Alexandre Iooss", + "Yoann Piétri", + "Charlie Jacomme", + "Corentin Canebier", + "Bombar Maxime", + "Guillaume Goessel", + "Matthieu Michelet", + "Edpibu", + "Fardale", + "Jean-marie Mineau", + "David Sinquin", + "Gabriel Le Bouder", + "Simon Brélivet", + "~anonymised~", + "Benjamin Graillot", + "Leïla Bekaddour", + "Éloi Alain", + "Pierre Cadart", + "Antoine Vintache", + "Thibault De Boutray", + "Delphine Salvy", + "Joanne Steiner", + "Krokmou", + "B", + "Daniel Stan", + "Gwenael Le Hir", + "Hugo Hervieux", + "Mikachu", + "Nymous", + "Pierre-antoine Comby", + "Vincent Le Gallic", +] diff --git a/re2o/field_permissions.py b/re2o/field_permissions.py index a35d206d..9a06cf80 100644 --- a/re2o/field_permissions.py +++ b/re2o/field_permissions.py @@ -40,8 +40,8 @@ class FieldPermissionModelMixin: FIELD_PERMISSION_MISSING_DEFAULT = True def has_field_perm(self, user, field): - """ Checks if a `user` has the right to edit the `field` - of this model """ + """Checks if a `user` has the right to edit the `field` + of this model""" if field in self.field_permissions: checks = self.field_permissions[field] if not isinstance(checks, (list, tuple)): diff --git a/re2o/login.py b/re2o/login.py index 0df5bf6d..c8248ae3 100644 --- a/re2o/login.py +++ b/re2o/login.py @@ -32,12 +32,12 @@ import binascii import crypt import hashlib import os -from base64 import encodestring, decodestring, b64encode, b64decode +from base64 import b64decode, b64encode, decodestring, encodestring from collections import OrderedDict -from django.contrib.auth import hashers -from django.contrib.auth.backends import ModelBackend from hmac import compare_digest as constant_time_compare +from django.contrib.auth import hashers +from django.contrib.auth.backends import ModelBackend ALGO_NAME = "{SSHA}" ALGO_LEN = len(ALGO_NAME + "$") @@ -45,7 +45,7 @@ DIGEST_LEN = 20 def makeSecret(password): - """ Build a hashed and salted version of the password with SSHA + """Build a hashed and salted version of the password with SSHA Parameters: password (string): Password to hash @@ -60,7 +60,7 @@ def makeSecret(password): def hashNT(password): - """ Build a md4 hash of the password to use as the NT-password + """Build a md4 hash of the password to use as the NT-password Parameters: password (string): Password to hash @@ -78,7 +78,7 @@ def checkPassword(challenge_password, password): Parameters: challenge_password (string): Password to verify with hash - password (string): Hashed password to verify + password (string): Hashed password to verify Returns: boolean: True if challenge_password and password match @@ -93,7 +93,7 @@ def checkPassword(challenge_password, password): def hash_password_salt(hashed_password): - """ Extract the salt from a given hashed password + """Extract the salt from a given hashed password Parameters: hashed_password (string): Hashed password to extract salt @@ -277,15 +277,17 @@ class SSHAPasswordHasher(hashers.BasePasswordHasher): class RecryptBackend(ModelBackend): """Function for legacy users. During auth, if their hash password is different from SSHA or ntlm password is empty, rehash in SSHA or NTLM - + Returns: model user instance: Instance of the user logged - + """ def authenticate(self, request, username=None, password=None, **kwargs): # we obtain from the classical auth backend the user - user = super(RecryptBackend, self).authenticate(request, username, password, **kwargs) + user = super(RecryptBackend, self).authenticate( + request, username, password, **kwargs + ) if user: if not (user.pwd_ntlm): # if we dont have NT hash, we create it diff --git a/re2o/mail_utils.py b/re2o/mail_utils.py index c80f2cfb..ec0b5d8c 100644 --- a/re2o/mail_utils.py +++ b/re2o/mail_utils.py @@ -25,11 +25,13 @@ All functions linked with emails here. Non model or app dependant """ -from django.utils.translation import ugettext_lazy as _ -from django.core.mail import send_mail as django_send_mail -from django.contrib import messages from smtplib import SMTPException -from socket import herror, gaierror +from socket import gaierror, herror + +from django.contrib import messages +from django.core.mail import send_mail as django_send_mail +from django.utils.translation import ugettext_lazy as _ + def send_mail(request, *args, **kwargs): """Wrapper for Django's send_mail which handles errors""" @@ -39,7 +41,8 @@ def send_mail(request, *args, **kwargs): except (SMTPException, ConnectionError, herror, gaierror) as e: messages.error( request, - _("Failed to send email: %(error)s.") % { + _("Failed to send email: %(error)s.") + % { "error": e, }, ) @@ -53,7 +56,8 @@ def send_mail_object(mail, request): if request: messages.error( request, - _("Failed to send email: %(error)s.") % { + _("Failed to send email: %(error)s.") + % { "error": e, }, ) diff --git a/re2o/management/commands/gen_contrib.py b/re2o/management/commands/gen_contrib.py index c5d693be..a4e810af 100644 --- a/re2o/management/commands/gen_contrib.py +++ b/re2o/management/commands/gen_contrib.py @@ -25,6 +25,7 @@ commits. This list is extracted from the current gitlab repository. """ import os + from django.core.management.base import BaseCommand diff --git a/re2o/mixins.py b/re2o/mixins.py index 22933e67..ddc827b6 100644 --- a/re2o/mixins.py +++ b/re2o/mixins.py @@ -23,9 +23,9 @@ A set of mixins used all over the project to avoid duplicating code """ -from reversion import revisions as reversion from django.db import transaction from django.utils.translation import ugettext as _ +from reversion import revisions as reversion class RevMixin(object): @@ -252,4 +252,3 @@ class AclMixin(object): else None, (permission,), ) - diff --git a/re2o/script_utils.py b/re2o/script_utils.py index 12464594..b82604ec 100644 --- a/re2o/script_utils.py +++ b/re2o/script_utils.py @@ -24,17 +24,16 @@ with Re2o throught the CLI """ import os -from os.path import dirname -import sys import pwd - +import sys from getpass import getpass -from reversion import revisions as reversion +from os.path import dirname -from django.core.wsgi import get_wsgi_application from django.core.management.base import CommandError +from django.core.wsgi import get_wsgi_application from django.db import transaction from django.utils.html import strip_tags +from reversion import revisions as reversion from users.models import User @@ -48,27 +47,24 @@ application = get_wsgi_application() def get_user(pseudo): """Find a user from its pseudo - + Parameters: pseudo (string): pseudo of this user Returns: user instance:Instance of user - + """ user = User.objects.filter(pseudo=pseudo) if len(user) == 0: raise CommandError("Invalid user.") if len(user) > 1: - raise CommandError( - "Several users match this username. This SHOULD NOT happen." - ) + raise CommandError("Several users match this username. This SHOULD NOT happen.") return user[0] def get_system_user(): - """Find the system user login who used the command - """ + """Find the system user login who used the command""" return pwd.getpwuid(int(os.getenv("SUDO_UID") or os.getuid())).pw_name @@ -80,7 +76,7 @@ def form_cli(Form, user, action, *args, **kwargs): Form : a django class form to fill-in user : a re2o user doign the modification action: the action done with that form, for logs purpose - + """ data = {} dumb_form = Form(user=user, *args, **kwargs) @@ -105,6 +101,4 @@ def form_cli(Form, user, action, *args, **kwargs): reversion.set_user(user) reversion.set_comment(action) - sys.stdout.write( - "%s: done. The edit may take several minutes to apply.\n" % action - ) + sys.stdout.write("%s: done. The edit may take several minutes to apply.\n" % action) diff --git a/re2o/settings.py b/re2o/settings.py index 66219032..158a278b 100644 --- a/re2o/settings.py +++ b/re2o/settings.py @@ -36,7 +36,9 @@ https://docs.djangoproject.com/en/1.8/ref/settings/ from __future__ import unicode_literals import os + from .settings_default import * + try: from .settings_local import * except ImportError: @@ -60,7 +62,11 @@ LOGIN_REDIRECT_URL = "/" # The URL for redirecting after login # Application definition # dal_legacy_static only needed for Django < 2.0 (https://django-autocomplete-light.readthedocs.io/en/master/install.html#django-versions-earlier-than-2-0) -EARLY_EXTERNAL_CONTRIB_APPS = ("dal", "dal_select2", "dal_legacy_static") # Need to be added before django.contrib.admin (https://django-autocomplete-light.readthedocs.io/en/master/install.html#configuration) +EARLY_EXTERNAL_CONTRIB_APPS = ( + "dal", + "dal_select2", + "dal_legacy_static", +) # Need to be added before django.contrib.admin (https://django-autocomplete-light.readthedocs.io/en/master/install.html#configuration) DJANGO_CONTRIB_APPS = ( "django.contrib.admin", "django.contrib.auth", @@ -82,7 +88,11 @@ LOCAL_APPS = ( "logs", ) INSTALLED_APPS = ( - EARLY_EXTERNAL_CONTRIB_APPS + DJANGO_CONTRIB_APPS + EXTERNAL_CONTRIB_APPS + LOCAL_APPS + OPTIONNAL_APPS + EARLY_EXTERNAL_CONTRIB_APPS + + DJANGO_CONTRIB_APPS + + EXTERNAL_CONTRIB_APPS + + LOCAL_APPS + + OPTIONNAL_APPS ) MIDDLEWARE = ( "django.middleware.security.SecurityMiddleware", @@ -196,18 +206,18 @@ EMAIL_TIMEOUT = 10 AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, -] +] # Activate API if "api" in INSTALLED_APPS: diff --git a/re2o/settings_default.py b/re2o/settings_default.py index d5ceb431..8f552c2b 100644 --- a/re2o/settings_default.py +++ b/re2o/settings_default.py @@ -56,7 +56,7 @@ GID_RANGES = {"posix": [501, 600]} # If you want to add a database routers, please fill in above and add your databse. # Then, add a file "local_routers.py" in folder app re2o, and add your router path in -# the LOCAL_ROUTERS var as "re2o.local_routers.DbRouter". You can also add extra routers. +# the LOCAL_ROUTERS var as "re2o.local_routers.DbRouter". You can also add extra routers. LOCAL_ROUTERS = [] # Some optionnal Re2o Apps @@ -65,24 +65,24 @@ OPTIONNAL_APPS_RE2O = () # Some Django apps you want to add in you local project OPTIONNAL_APPS = OPTIONNAL_APPS_RE2O + () -#Set auth password validator +# Set auth password validator AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - 'OPTIONS': { - 'user_attributes': ['surname', 'pseudo', 'name', 'email'], - } + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + "OPTIONS": { + "user_attributes": ["surname", "pseudo", "name", "email"], + }, }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - 'OPTIONS': { - 'min_length': 8, - } + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "OPTIONS": { + "min_length": 8, + }, }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] diff --git a/re2o/settings_local.example.py b/re2o/settings_local.example.py index d557d7eb..fc3cd8fc 100644 --- a/re2o/settings_local.example.py +++ b/re2o/settings_local.example.py @@ -105,7 +105,7 @@ GID_RANGES = {"posix": [501, 600]} # If you want to add a database routers, please fill in above and add your databse. # Then, add a file "local_routers.py" in folder app re2o, and add your router path in -# the LOCAL_ROUTERS var as "re2o.local_routers.DbRouter". You can also add extra routers. +# the LOCAL_ROUTERS var as "re2o.local_routers.DbRouter". You can also add extra routers. LOCAL_ROUTERS = [] # Some optionnal Re2o Apps @@ -114,24 +114,24 @@ OPTIONNAL_APPS_RE2O = () # Some Django apps you want to add in you local project OPTIONNAL_APPS = OPTIONNAL_APPS_RE2O + () -#Set auth password validator +# Set auth password validator AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - 'OPTIONS': { - 'user_attributes': ['surname', 'pseudo', 'name', 'email'], - } + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + "OPTIONS": { + "user_attributes": ["surname", "pseudo", "name", "email"], + }, }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - 'OPTIONS': { - 'min_length': 8, - } + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "OPTIONS": { + "min_length": 8, + }, }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] diff --git a/re2o/templatetags/acl.py b/re2o/templatetags/acl.py index d8bdc310..0079c00e 100644 --- a/re2o/templatetags/acl.py +++ b/re2o/templatetags/acl.py @@ -73,9 +73,8 @@ an instance of a model (either Model.can_xxx or instance.can_xxx) import sys from django import template -from django.template.base import Node, NodeList from django.contrib.contenttypes.models import ContentType - +from django.template.base import Node, NodeList register = template.Library() @@ -202,7 +201,7 @@ def acl_fct(callback, reverse): @register.tag("cannot_edit_history") def acl_history_filter(parser, token): """Templatetag for acl checking on history.""" - tag_name, = token.split_contents() + (tag_name,) = token.split_contents() callback = get_callback(tag_name) oknodes = parser.parse(("acl_else", "acl_end")) diff --git a/re2o/templatetags/pagination_extra.py b/re2o/templatetags/pagination_extra.py index eb50a90d..405a4aef 100644 --- a/re2o/templatetags/pagination_extra.py +++ b/re2o/templatetags/pagination_extra.py @@ -20,10 +20,12 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. from django import template + from .url_insert_param import url_insert_param register = template.Library() + @register.simple_tag def pagination_insert_page_and_id(url, page=1, id=None, **kwargs): """ @@ -77,10 +79,14 @@ def pagination_insert_page_and_id(url, page=1, id=None, **kwargs): """ page_arg = "page" - if "page_arg" in kwargs and kwargs["page_arg"] is not None and len(kwargs["page_arg"]) > 0: + if ( + "page_arg" in kwargs + and kwargs["page_arg"] is not None + and len(kwargs["page_arg"]) > 0 + ): page_arg = kwargs["page_arg"] - args = { "url": url, page_arg: page} + args = {"url": url, page_arg: page} new_url = url_insert_param(**args) if id != None: diff --git a/re2o/templatetags/self_adhesion.py b/re2o/templatetags/self_adhesion.py index 31f8ccc9..2e446106 100644 --- a/re2o/templatetags/self_adhesion.py +++ b/re2o/templatetags/self_adhesion.py @@ -26,8 +26,8 @@ which indicated if a user can creates an account by himself """ from django import template -from preferences.models import OptionalUser +from preferences.models import OptionalUser register = template.Library() diff --git a/re2o/urls.py b/re2o/urls.py index ece23894..82ecb855 100644 --- a/re2o/urls.py +++ b/re2o/urls.py @@ -24,14 +24,13 @@ from __future__ import unicode_literals from django.conf import settings -from django.urls import include, path from django.contrib import admin +from django.urls import include, path from django.utils.translation import ugettext_lazy as _ from django.views.generic import RedirectView from .settings_local import OPTIONNAL_APPS_RE2O - -from .views import index, about_page, contact_page, handler404, handler500 +from .views import about_page, contact_page, handler404, handler500, index # Admin site configuration admin.site.index_title = _("Homepage") diff --git a/re2o/utils.py b/re2o/utils.py index b7a0cd23..9fff653c 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -36,14 +36,14 @@ Functions: from __future__ import unicode_literals -from django.utils import timezone +from django.contrib.auth.models import Group, Permission from django.db.models import Q -from django.contrib.auth.models import Permission, Group +from django.utils import timezone from cotisations.models import Cotisation, Facture, Vente from machines.models import Interface, Machine -from users.models import Adherent, User, Ban, Whitelist from preferences.models import AssoOption +from users.models import Adherent, Ban, User, Whitelist def get_group_having_permission(*permission_name): @@ -83,7 +83,9 @@ def filter_results(query_filter, dormitory, user_type): - on user_type (adherent or club) is specified Returns the filter""" if dormitory: - query_filter &= (Q(adherent__room__building__dormitory=dormitory) | Q(club__room__building__dormitory=dormitory)) + query_filter &= Q(adherent__room__building__dormitory=dormitory) | Q( + club__room__building__dormitory=dormitory + ) if user_type == "adherent": query_filter &= Q(adherent__isnull=False) if user_type == "club": @@ -91,18 +93,20 @@ def filter_results(query_filter, dormitory, user_type): return query_filter -def all_adherent(search_time=None, including_asso=True, dormitory=None, user_type="all"): +def all_adherent( + search_time=None, including_asso=True, dormitory=None, user_type="all" +): """Return all people who have a valid membership at org. Optimised to make only one sql query. Build a filter and then apply it to User. Check for each user if a valid membership is registered at the desired search_time. - + Parameters: search_time (django datetime): Datetime to perform this search, if not provided, search_time will be set à timezone.now() including_asso (boolean): Decide if org itself is included in results Returns: - django queryset: Django queryset containing all users with valid membership + django queryset: Django queryset containing all users with valid membership """ if search_time is None: @@ -110,9 +114,9 @@ def all_adherent(search_time=None, including_asso=True, dormitory=None, user_typ filter_user = Q( facture__in=Facture.objects.filter( vente__cotisation__in=Cotisation.objects.filter( - Q(vente__facture__facture__valid=True) & - Q(date_start_memb__lt=search_time) & - Q(date_end_memb__gt=search_time) + Q(vente__facture__facture__valid=True) + & Q(date_start_memb__lt=search_time) + & Q(date_end_memb__gt=search_time) ) ) ) @@ -126,15 +130,15 @@ def all_adherent(search_time=None, including_asso=True, dormitory=None, user_typ def all_baned(search_time=None, dormitory=None, user_type="all"): """Return all people who are banned at org. Optimised to make only one - sql query. Build a filter and then apply it to User. Check for each user + sql query. Build a filter and then apply it to User. Check for each user banned at the desired search_time. - + Parameters: search_time (django datetime): Datetime to perform this search, if not provided, search_time will be set à timezone.now() Returns: - django queryset: Django queryset containing all users banned + django queryset: Django queryset containing all users banned """ if search_time is None: @@ -152,13 +156,13 @@ def all_whitelisted(search_time=None, dormitory=None, user_type="all"): """Return all people who have a free access at org. Optimised to make only one sql query. Build a filter and then apply it to User. Check for each user with a whitelisted free access at the desired search_time. - + Parameters: search_time (django datetime): Datetime to perform this search, if not provided, search_time will be set à timezone.now() Returns: - django queryset: Django queryset containing all users whitelisted + django queryset: Django queryset containing all users whitelisted """ if search_time is None: @@ -191,9 +195,9 @@ def all_conn(search_time=None, including_asso=True, dormitory=None, user_type="a filter_user = Q( facture__in=Facture.objects.filter( vente__cotisation__in=Cotisation.objects.filter( - Q(vente__facture__facture__valid=True) & - Q(date_start_con__lt=search_time) & - Q(date_end_con__gt=search_time) + Q(vente__facture__facture__valid=True) + & Q(date_start_con__lt=search_time) + & Q(date_end_con__gt=search_time) ) ) ) @@ -205,9 +209,11 @@ def all_conn(search_time=None, including_asso=True, dormitory=None, user_type="a return User.objects.filter(filter_user).distinct() -def all_has_access(search_time=None, including_asso=True, dormitory=None, user_type="all"): +def all_has_access( + search_time=None, including_asso=True, dormitory=None, user_type="all" +): """Return all people who have an valid internet access at org. Call previously buid filters. - Can't do that in one sql query unfortunatly. Apply each filters, and return users + Can't do that in one sql query unfortunatly. Apply each filters, and return users with a whitelist, or a valid paid access, except banned users. Parameters: @@ -216,14 +222,13 @@ def all_has_access(search_time=None, including_asso=True, dormitory=None, user_t including_asso (boolean): Decide if org itself is included in results Returns: - django queryset: Django queryset containing all valid connection users + django queryset: Django queryset containing all valid connection users """ if search_time is None: search_time = timezone.now() - filter_user = ( - Q(state=User.STATE_ACTIVE) - & ~Q(email_state=User.EMAIL_STATE_UNVERIFIED) + filter_user = Q(state=User.STATE_ACTIVE) & ~Q( + email_state=User.EMAIL_STATE_UNVERIFIED ) if including_asso: asso_user = AssoOption.get_cached_value("utilisateur_asso") @@ -231,18 +236,36 @@ def all_has_access(search_time=None, including_asso=True, dormitory=None, user_t filter_user |= Q(id=asso_user.id) filter_user = filter_results(filter_user, dormitory, user_type) return User.objects.filter( - Q(filter_user) & ( + Q(filter_user) + & ( Q( - id__in=all_whitelisted(search_time=search_time, dormitory=dormitory, user_type=user_type) - ) | ( - Q( - id__in=all_adherent(search_time=search_time, including_asso=including_asso, dormitory=dormitory, user_type=user_type) - ) & Q( - id__in=all_conn(search_time=search_time, including_asso=including_asso, dormitory=dormitory, user_type=user_type) + id__in=all_whitelisted( + search_time=search_time, dormitory=dormitory, user_type=user_type ) ) - ) & ~Q( - id__in=all_baned(search_time=search_time, dormitory=dormitory, user_type=user_type) + | ( + Q( + id__in=all_adherent( + search_time=search_time, + including_asso=including_asso, + dormitory=dormitory, + user_type=user_type, + ) + ) + & Q( + id__in=all_conn( + search_time=search_time, + including_asso=including_asso, + dormitory=dormitory, + user_type=user_type, + ) + ) + ) + ) + & ~Q( + id__in=all_baned( + search_time=search_time, dormitory=dormitory, user_type=user_type + ) ) ).distinct() @@ -257,7 +280,7 @@ def filter_active_interfaces(interface_set): interface_set (django queryset): A queryset of interfaces to perform filter Returns: - django filter: Django filter to apply to an interfaces queryset, + django filter: Django filter to apply to an interfaces queryset, will return when applied all active interfaces, related with a user with valid membership @@ -289,7 +312,7 @@ def filter_complete_interfaces(interface_set): interface_set (django queryset): A queryset of interfaces to perform filter Returns: - django filter: Django filter to apply to an interfaces queryset, + django filter: Django filter to apply to an interfaces queryset, will return when applied all active interfaces, related with a user with valid membership diff --git a/re2o/views.py b/re2o/views.py index f9bcb322..07f55253 100644 --- a/re2o/views.py +++ b/re2o/views.py @@ -26,34 +26,28 @@ Welcom main page view, and several template widely used in re2o views from __future__ import unicode_literals -import git +from importlib import import_module +import git +from dal import autocomplete +from django.conf import settings +from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render from django.template.context_processors import csrf -from django.conf import settings -from django.utils.translation import ugettext as _ -from django.contrib.auth.mixins import LoginRequiredMixin from django.utils.decorators import method_decorator -from dal import autocomplete +from django.utils.translation import ugettext as _ -from preferences.models import ( - Service, - MailContact, - AssoOption, - HomeOption, - GeneralOption, - Mandate, -) +from preferences.models import (AssoOption, GeneralOption, HomeOption, + MailContact, Mandate, Service) +from re2o.settings_local import OPTIONNAL_APPS_RE2O from .contributors import CONTRIBUTORS -from importlib import import_module -from re2o.settings_local import OPTIONNAL_APPS_RE2O def form(ctx, template, request): """Global template function, used in all re2o views, for building a render with context, template and request. Adding csrf. - + Parameters: ctx (dict): Dict of values to transfer to template template (django template): The django template of this view @@ -70,8 +64,8 @@ def form(ctx, template, request): def index(request): """Display all services provided on main page - Returns: a form with all services linked and description, and social media - link if provided. + Returns: a form with all services linked and description, and social media + link if provided. """ services = [[], [], []] @@ -95,9 +89,9 @@ def index(request): def about_page(request): - """ The view for the about page. + """The view for the about page. Fetch some info about the configuration of the project. If it can't - get the info from the Git repository, fallback to default string """ + get the info from the Git repository, fallback to default string""" option = AssoOption.objects.get() general = GeneralOption.objects.get() git_info_contributors = CONTRIBUTORS @@ -197,4 +191,3 @@ class AutocompleteLoggedOutViewMixin(autocomplete.Select2QuerySetView): class AutocompleteViewMixin(LoginRequiredMixin, AutocompleteLoggedOutViewMixin): pass - diff --git a/re2o/widgets.py b/re2o/widgets.py index 935806fe..32b49724 100644 --- a/re2o/widgets.py +++ b/re2o/widgets.py @@ -4,7 +4,7 @@ # quelques clics. # # Copyright © 2021 Gabriel Détraz -# Copyright © 2021 Jean-Romain Garnier +# Copyright © 2021 Jean-Romain Garnier # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,12 +25,12 @@ Re2o Forms and ModelForms Widgets. Used in others forms for using autocomplete engine. """ -from django.utils.translation import ugettext as _ from dal import autocomplete +from django.utils.translation import ugettext as _ class AutocompleteModelWidget(autocomplete.ModelSelect2): - """ A mixin subclassing django-autocomplete-light's Select2 model to pass default options + """A mixin subclassing django-autocomplete-light's Select2 model to pass default options See https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#passing-options-to-select2 """ @@ -54,7 +54,7 @@ class AutocompleteModelWidget(autocomplete.ModelSelect2): class AutocompleteMultipleModelWidget(autocomplete.ModelSelect2Multiple): - """ A mixin subclassing django-autocomplete-light's Select2 model to pass default options + """A mixin subclassing django-autocomplete-light's Select2 model to pass default options See https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#passing-options-to-select2 """ diff --git a/re2o/wsgi.py b/re2o/wsgi.py index 4f8d2196..2f67a767 100644 --- a/re2o/wsgi.py +++ b/re2o/wsgi.py @@ -32,12 +32,11 @@ https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ from __future__ import unicode_literals import os -from os.path import dirname import sys +from os.path import dirname from django.core.wsgi import get_wsgi_application - sys.path.append(dirname(dirname(__file__))) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings") diff --git a/search/apps.py b/search/apps.py index 1dad5588..b7754062 100644 --- a/search/apps.py +++ b/search/apps.py @@ -8,4 +8,4 @@ from django.apps import AppConfig class SearchConfig(AppConfig): """Configuration of search app.""" - name = "search" \ No newline at end of file + name = "search" diff --git a/search/engine.py b/search/engine.py index a724cd8c..4202df1f 100644 --- a/search/engine.py +++ b/search/engine.py @@ -28,19 +28,16 @@ Gplv2""" from __future__ import unicode_literals +from django.db.models import Q, Value +from django.db.models.functions import Concat from netaddr import EUI, AddrFormatError -from django.db.models import Q -from django.db.models import Value -from django.db.models.functions import Concat - -from users.models import User, Adherent, Club, Ban, Whitelist -from machines.models import Machine -from topologie.models import Port, Switch, Room from cotisations.models import Facture +from machines.models import Machine from preferences.models import GeneralOption from re2o.base import SortTable, re2o_paginator - +from topologie.models import Port, Room, Switch +from users.models import Adherent, Ban, Club, User, Whitelist # List of fields the search applies to FILTER_FIELDS = [ diff --git a/search/forms.py b/search/forms.py index 35151a3a..266eaaab 100644 --- a/search/forms.py +++ b/search/forms.py @@ -27,6 +27,7 @@ from __future__ import unicode_literals from django import forms from django.forms import Form from django.utils.translation import ugettext_lazy as _ + from re2o.base import get_input_formats_help_text CHOICES_USER = ( diff --git a/search/views.py b/search/views.py index afb6065e..20206858 100644 --- a/search/views.py +++ b/search/views.py @@ -28,24 +28,18 @@ Gplv2""" from __future__ import unicode_literals -from django.shortcuts import render from django.contrib.auth.decorators import login_required +from django.shortcuts import render -from users.models import User from cotisations.models import Cotisation from machines.models import Machine -from search.forms import ( - SearchForm, - SearchFormPlus, - CHOICES_USER, - CHOICES_EMAILS, - CHOICES_AFF, - initial_choices, -) from re2o.acl import can_view_all +from search.forms import (CHOICES_AFF, CHOICES_EMAILS, CHOICES_USER, + SearchForm, SearchFormPlus, initial_choices) +from users.models import User -from .engine import empty_filters, create_queries, search_single_query -from .engine import apply_filters, finish_results +from .engine import (apply_filters, create_queries, empty_filters, + finish_results, search_single_query) def get_results(query, request, params): @@ -70,10 +64,7 @@ def get_results(query, request, params): results = apply_filters(filters, request.user, aff) results = finish_results( - request, - results, - request.GET.get("col"), - request.GET.get("order") + request, results, request.GET.get("col"), request.GET.get("order") ) results.update({"search_term": query}) @@ -90,9 +81,7 @@ def search(request): request, "search/index.html", get_results( - search_form.cleaned_data.get("q", ""), - request, - search_form.cleaned_data + search_form.cleaned_data.get("q", ""), request, search_form.cleaned_data ), ) return render(request, "search/search.html", {"search_form": search_form}) @@ -108,9 +97,7 @@ def searchp(request): request, "search/index.html", get_results( - search_form.cleaned_data.get("q", ""), - request, - search_form.cleaned_data + search_form.cleaned_data.get("q", ""), request, search_form.cleaned_data ), ) return render(request, "search/search.html", {"search_form": search_form}) diff --git a/test_utils/runner.py b/test_utils/runner.py index 888e8a02..219b6a2c 100644 --- a/test_utils/runner.py +++ b/test_utils/runner.py @@ -23,13 +23,14 @@ """Defines the custom runners for Re2o. """ -import volatildap import os.path -from django.test.runner import DiscoverRunner +import volatildap from django.conf import settings +from django.test.runner import DiscoverRunner -from users.models import LdapUser, LdapUserGroup, LdapServiceUser, LdapServiceUserGroup +from users.models import (LdapServiceUser, LdapServiceUserGroup, LdapUser, + LdapUserGroup) # The path of this file __here = os.path.dirname(os.path.realpath(__file__)) diff --git a/tickets/admin.py b/tickets/admin.py index 620fc998..6d34281f 100644 --- a/tickets/admin.py +++ b/tickets/admin.py @@ -25,15 +25,18 @@ Ticket preferences model from django.contrib import admin -from .models import Ticket, CommentTicket - from reversion.admin import VersionAdmin +from .models import CommentTicket, Ticket + + class TicketAdmin(VersionAdmin): pass + class CommentTicketAdmin(VersionAdmin): pass + admin.site.register(Ticket, TicketAdmin) admin.site.register(CommentTicket, CommentTicketAdmin) diff --git a/tickets/forms.py b/tickets/forms.py index aad78837..d4a1d5fb 100644 --- a/tickets/forms.py +++ b/tickets/forms.py @@ -25,14 +25,15 @@ Ticket form from django import forms +from django.forms import Form, ModelForm from django.template.loader import render_to_string -from django.forms import ModelForm, Form +from django.utils.translation import ugettext_lazy as _ + from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin from re2o.widgets import AutocompleteModelWidget -from django.utils.translation import ugettext_lazy as _ -from .models import Ticket, CommentTicket +from .models import CommentTicket, Ticket class NewTicketForm(FormRevMixin, ModelForm): @@ -46,10 +47,12 @@ class NewTicketForm(FormRevMixin, ModelForm): request = kwargs.pop("request", None) super(NewTicketForm, self).__init__(*args, **kwargs) if request.user.is_authenticated: - self.fields.pop('email') + self.fields.pop("email") self.instance.user = request.user - self.fields['description'].help_text = render_to_string('tickets/help_text.html') - self.instance.language = getattr(request, "LANGUAGE_CODE", "en") + self.fields["description"].help_text = render_to_string( + "tickets/help_text.html" + ) + self.instance.language = getattr(request, "LANGUAGE_CODE", "en") self.instance.request = request @@ -67,7 +70,7 @@ class EditTicketForm(FormRevMixin, ModelForm): def __init__(self, *args, **kwargs): super(EditTicketForm, self).__init__(*args, **kwargs) - self.fields['email'].required = False + self.fields["email"].required = False class CommentTicketForm(FormRevMixin, ModelForm): @@ -83,4 +86,3 @@ class CommentTicketForm(FormRevMixin, ModelForm): super(CommentTicketForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields["comment"].label = _("comment") self.instance.request = request - diff --git a/tickets/models.py b/tickets/models.py index 3bae040a..bc62e22b 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -25,22 +25,19 @@ Ticket model from __future__ import absolute_import +from django.core.mail import EmailMessage from django.db import models -from django.utils.translation import ugettext_lazy as _ -from django.template import loader from django.db.models.signals import post_save from django.dispatch import receiver +from django.template import loader from django.utils.functional import cached_property - +from django.utils.translation import ugettext_lazy as _ from reversion.models import Version -from re2o.mixins import AclMixin -from re2o.mail_utils import send_mail_object -from django.core.mail import EmailMessage - -from preferences.models import GeneralOption - import users.models +from preferences.models import GeneralOption +from re2o.mail_utils import send_mail_object +from re2o.mixins import AclMixin from .preferences.models import TicketOption @@ -85,7 +82,7 @@ class Ticket(AclMixin, models.Model): ) solved = models.BooleanField(default=False) language = models.CharField( - max_length=16, help_text=_("Language of the ticket."), default="en" + max_length=16, help_text=_("Language of the ticket."), default="en" ) request = None @@ -96,7 +93,9 @@ class Ticket(AclMixin, models.Model): def __str__(self): if self.user: - return _("Ticket from {name}. Date: {date}.").format(name=self.user.get_full_name(),date=self.date) + return _("Ticket from {name}. Date: {date}.").format( + name=self.user.get_full_name(), date=self.date + ) else: return _("Anonymous ticket. Date: %s.") % (self.date) @@ -106,12 +105,12 @@ class Ticket(AclMixin, models.Model): if self.user: return self.user.get_full_name() else: - return _("Anonymous user") + return _("Anonymous user") @cached_property def get_mail(self): """Get the email address of the user who opened the ticket.""" - return self.email or self.user.get_mail + return self.email or self.user.get_mail def publish_mail(self): """Send an email for a newly opened ticket to the address set in the @@ -137,7 +136,6 @@ class Ticket(AclMixin, models.Model): ) send_mail_object(mail_to_send, self.request) - def can_view(self, user_request, *_args, **_kwargs): """Check that the user has the right to view the ticket or that it is the author.""" @@ -189,9 +187,7 @@ class CommentTicket(AclMixin, models.Model): blank=False, null=False, ) - parent_ticket = models.ForeignKey( - "Ticket", on_delete=models.CASCADE - ) + parent_ticket = models.ForeignKey("Ticket", on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) created_by = models.ForeignKey( "users.User", @@ -207,7 +203,12 @@ class CommentTicket(AclMixin, models.Model): @cached_property def comment_id(self): - return CommentTicket.objects.filter(parent_ticket=self.parent_ticket, pk__lt=self.pk).count() + 1 + return ( + CommentTicket.objects.filter( + parent_ticket=self.parent_ticket, pk__lt=self.pk + ).count() + + 1 + ) def can_view(self, user_request, *_args, **_kwargs): """Check that the user has the right to view the ticket comment @@ -218,7 +219,9 @@ class CommentTicket(AclMixin, models.Model): ): return ( False, - _("You don't have the right to view other tickets comments than yours."), + _( + "You don't have the right to view other tickets comments than yours." + ), ("tickets.view_commentticket",), ) else: @@ -227,13 +230,15 @@ class CommentTicket(AclMixin, models.Model): def can_edit(self, user_request, *_args, **_kwargs): """Check that the user has the right to edit the ticket comment or that it is the author.""" - if ( - not user_request.has_perm("tickets.change_commentticket") - and (self.parent_ticket.user != user_request or self.parent_ticket.user != self.created_by) + if not user_request.has_perm("tickets.change_commentticket") and ( + self.parent_ticket.user != user_request + or self.parent_ticket.user != self.created_by ): return ( False, - _("You don't have the right to edit other tickets comments than yours."), + _( + "You don't have the right to edit other tickets comments than yours." + ), ("tickets.change_commentticket",), ) else: diff --git a/tickets/preferences/__init__.py b/tickets/preferences/__init__.py index 2147642b..441f485d 100644 --- a/tickets/preferences/__init__.py +++ b/tickets/preferences/__init__.py @@ -4,7 +4,7 @@ # quelques clics. # # Copyright © 2020 Gabriel Détraz -# Copyright © 2019 Arthur Grisel-Davy +# Copyright © 2019 Arthur Grisel-Davy # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/tickets/preferences/forms.py b/tickets/preferences/forms.py index ff76f1d9..b043c517 100644 --- a/tickets/preferences/forms.py +++ b/tickets/preferences/forms.py @@ -24,10 +24,11 @@ Ticket preferences form """ from django import forms -from django.forms import ModelForm, Form +from django.forms import Form, ModelForm from django.utils.translation import ugettext_lazy as _ from re2o.mixins import FormRevMixin + from .models import TicketOption diff --git a/tickets/preferences/models.py b/tickets/preferences/models.py index 67f8435a..92d8e8c5 100644 --- a/tickets/preferences/models.py +++ b/tickets/preferences/models.py @@ -27,8 +27,8 @@ Ticket preferences model from django.db import models from django.utils.translation import ugettext_lazy as _ -from re2o.mixins import AclMixin, RevMixin from preferences.models import PreferencesModel +from re2o.mixins import AclMixin, RevMixin class TicketOption(AclMixin, PreferencesModel): diff --git a/tickets/preferences/views.py b/tickets/preferences/views.py index e1549fd2..a59b4db2 100644 --- a/tickets/preferences/views.py +++ b/tickets/preferences/views.py @@ -26,19 +26,16 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.shortcuts import render, redirect +from django.shortcuts import redirect, render from django.template.loader import render_to_string -from django.utils.translation import ugettext as _ from django.urls import reverse - -from re2o.base import re2o_paginator - -from re2o.acl import can_view, can_view_all, can_edit, can_create +from django.utils.translation import ugettext as _ from preferences.views import edit_options_template_function +from re2o.acl import can_create, can_edit, can_view, can_view_all +from re2o.base import re2o_paginator -from . import forms -from . import models +from . import forms, models def aff_preferences(request): diff --git a/tickets/urls.py b/tickets/urls.py index c853fcfc..a31cbb35 100644 --- a/tickets/urls.py +++ b/tickets/urls.py @@ -33,7 +33,11 @@ app_name = "tickets" urlpatterns = [ path("", views.aff_tickets, name="aff-tickets"), path("", views.aff_ticket, name="aff-ticket"), - path("change_ticket_status/", views.change_ticket_status, name="change-ticket-status"), + path( + "change_ticket_status/", + views.change_ticket_status, + name="change-ticket-status", + ), path("edit_ticket/", views.edit_ticket, name="edit-ticket"), re_path( r"^edit_options/(?P
TicketOption)$", diff --git a/tickets/views.py b/tickets/views.py index f0736265..d1a4d433 100644 --- a/tickets/views.py +++ b/tickets/views.py @@ -22,29 +22,20 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.shortcuts import render, redirect -from django.template.loader import render_to_string -from django.views.decorators.cache import cache_page -from django.utils.translation import ugettext as _ -from django.urls import reverse from django.forms import modelformset_factory -from re2o.views import form - -from re2o.base import re2o_paginator - -from re2o.acl import ( - can_view, - can_view_all, - can_edit, - can_create, - can_delete -) +from django.shortcuts import redirect, render +from django.template.loader import render_to_string +from django.urls import reverse +from django.utils.translation import ugettext as _ +from django.views.decorators.cache import cache_page from preferences.models import GeneralOption +from re2o.acl import can_create, can_delete, can_edit, can_view, can_view_all +from re2o.base import re2o_paginator +from re2o.views import form -from .models import Ticket, CommentTicket - -from .forms import NewTicketForm, EditTicketForm, CommentTicketForm +from .forms import CommentTicketForm, EditTicketForm, NewTicketForm +from .models import CommentTicket, Ticket def new_ticket(request): @@ -62,10 +53,12 @@ def new_ticket(request): return redirect(reverse("index")) else: return redirect( - reverse("users:profil", kwargs={"userid": str(request.user.id)}) + reverse("users:profil", kwargs={"userid": str(request.user.id)}) ) return form( - {"ticketform": ticketform, 'action_name': ("Create a ticket")}, "tickets/edit.html", request + {"ticketform": ticketform, "action_name": ("Create a ticket")}, + "tickets/edit.html", + request, ) @@ -87,9 +80,7 @@ def change_ticket_status(request, ticket, ticketid): """View used to change a ticket's status.""" ticket.solved = not ticket.solved ticket.save() - return redirect( - reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}) - ) + return redirect(reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)})) @login_required @@ -101,15 +92,15 @@ def edit_ticket(request, ticket, ticketid): ticketform.save() messages.success( request, - _( - "Ticket has been updated successfully" - ), + _("Ticket has been updated successfully"), ) return redirect( reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}) ) return form( - {"ticketform": ticketform, 'action_name': ("Edit this ticket")}, "tickets/edit.html", request + {"ticketform": ticketform, "action_name": ("Edit this ticket")}, + "tickets/edit.html", + request, ) @@ -128,7 +119,9 @@ def add_comment(request, ticket, ticketid): reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}) ) return form( - {"ticketform": commentticket, "action_name": _("Add a comment")}, "tickets/edit.html", request + {"ticketform": commentticket, "action_name": _("Add a comment")}, + "tickets/edit.html", + request, ) @@ -136,7 +129,9 @@ def add_comment(request, ticket, ticketid): @can_edit(CommentTicket) def edit_comment(request, commentticket_instance, **_kwargs): """View used to edit a comment of a ticket.""" - commentticket = CommentTicketForm(request.POST or None, instance=commentticket_instance) + commentticket = CommentTicketForm( + request.POST or None, instance=commentticket_instance + ) if commentticket.is_valid(): ticketid = commentticket_instance.parent_ticket.id if commentticket.changed_data: @@ -146,7 +141,9 @@ def edit_comment(request, commentticket_instance, **_kwargs): reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}) ) return form( - {"ticketform": commentticket, "action_name": _("Edit")}, "tickets/edit.html", request, + {"ticketform": commentticket, "action_name": _("Edit")}, + "tickets/edit.html", + request, ) @@ -162,7 +159,9 @@ def del_comment(request, commentticket, **_kwargs): reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}) ) return form( - {"objet": commentticket, "objet_name": _("Ticket Comment")}, "tickets/delete.html", request + {"objet": commentticket, "objet_name": _("Ticket Comment")}, + "tickets/delete.html", + request, ) diff --git a/topologie/admin.py b/topologie/admin.py index 514af42d..33441ddd 100644 --- a/topologie/admin.py +++ b/topologie/admin.py @@ -29,19 +29,9 @@ from __future__ import unicode_literals from django.contrib import admin from reversion.admin import VersionAdmin -from .models import ( - Port, - Room, - Switch, - Stack, - ModelSwitch, - ConstructorSwitch, - AccessPoint, - SwitchBay, - Building, - Dormitory, - PortProfile, -) +from .models import (AccessPoint, Building, ConstructorSwitch, Dormitory, + ModelSwitch, Port, PortProfile, Room, Stack, Switch, + SwitchBay) class StackAdmin(VersionAdmin): diff --git a/topologie/api/serializers.py b/topologie/api/serializers.py index 0dd9f599..e6f3dbb2 100644 --- a/topologie/api/serializers.py +++ b/topologie/api/serializers.py @@ -21,15 +21,15 @@ from rest_framework import serializers -import topologie.models as topologie import machines.models as machines -from machines.api.serializers import VlanSerializer, Ipv6ListSerializer -from api.serializers import NamespacedHRField, NamespacedHIField, NamespacedHMSerializer +import topologie.models as topologie +from api.serializers import (NamespacedHIField, NamespacedHMSerializer, + NamespacedHRField) +from machines.api.serializers import Ipv6ListSerializer, VlanSerializer class StackSerializer(NamespacedHMSerializer): - """Serialize `topologie.models.Stack` objects - """ + """Serialize `topologie.models.Stack` objects""" class Meta: model = topologie.Stack @@ -44,8 +44,7 @@ class StackSerializer(NamespacedHMSerializer): class AccessPointSerializer(NamespacedHMSerializer): - """Serialize `topologie.models.AccessPoint` objects - """ + """Serialize `topologie.models.AccessPoint` objects""" class Meta: model = topologie.AccessPoint @@ -53,8 +52,7 @@ class AccessPointSerializer(NamespacedHMSerializer): class SwitchSerializer(NamespacedHMSerializer): - """Serialize `topologie.models.Switch` objects - """ + """Serialize `topologie.models.Switch` objects""" port_amount = serializers.IntegerField(source="number") @@ -74,8 +72,7 @@ class SwitchSerializer(NamespacedHMSerializer): class ServerSerializer(NamespacedHMSerializer): - """Serialize `topologie.models.Server` objects - """ + """Serialize `topologie.models.Server` objects""" class Meta: model = topologie.Server @@ -83,8 +80,7 @@ class ServerSerializer(NamespacedHMSerializer): class ModelSwitchSerializer(NamespacedHMSerializer): - """Serialize `topologie.models.ModelSwitch` objects - """ + """Serialize `topologie.models.ModelSwitch` objects""" class Meta: model = topologie.ModelSwitch @@ -92,8 +88,7 @@ class ModelSwitchSerializer(NamespacedHMSerializer): class ConstructorSwitchSerializer(NamespacedHMSerializer): - """Serialize `topologie.models.ConstructorSwitch` objects - """ + """Serialize `topologie.models.ConstructorSwitch` objects""" class Meta: model = topologie.ConstructorSwitch @@ -101,8 +96,7 @@ class ConstructorSwitchSerializer(NamespacedHMSerializer): class SwitchBaySerializer(NamespacedHMSerializer): - """Serialize `topologie.models.SwitchBay` objects - """ + """Serialize `topologie.models.SwitchBay` objects""" class Meta: model = topologie.SwitchBay @@ -110,23 +104,23 @@ class SwitchBaySerializer(NamespacedHMSerializer): class BuildingSerializer(NamespacedHMSerializer): - """Serialize `topologie.models.Building` objects - """ + """Serialize `topologie.models.Building` objects""" class Meta: model = topologie.Building fields = ("name", "dormitory", "api_url") + class DormitorySerializer(NamespacedHMSerializer): - """Serialize `topologie.models.Dormitory` objects - """ + """Serialize `topologie.models.Dormitory` objects""" + class Meta: model = topologie.Dormitory fields = ("name", "api_url") + class PortProfileSerializer(NamespacedHMSerializer): - """Serialize `topologie.models.Room` objects - """ + """Serialize `topologie.models.Room` objects""" class Meta: model = topologie.PortProfile @@ -151,8 +145,7 @@ class PortProfileSerializer(NamespacedHMSerializer): class RoomSerializer(NamespacedHMSerializer): - """Serialize `topologie.models.Room` objects - """ + """Serialize `topologie.models.Room` objects""" class Meta: model = topologie.Room @@ -208,8 +201,7 @@ class InterfaceRoleSerializer(NamespacedHMSerializer): class RoleSerializer(NamespacedHMSerializer): - """Serialize `machines.models.OuverturePort` objects. - """ + """Serialize `machines.models.OuverturePort` objects.""" servers = InterfaceRoleSerializer(read_only=True, many=True) @@ -265,8 +257,7 @@ class SwitchBaySerializer(NamespacedHMSerializer): class PortsSerializer(NamespacedHMSerializer): - """Serialize `machines.models.Ipv6List` objects. - """ + """Serialize `machines.models.Ipv6List` objects.""" get_port_profile = ProfilSerializer(read_only=True) @@ -301,5 +292,3 @@ class SwitchPortSerializer(serializers.ModelSerializer): "get_radius_servers", "list_modules", ) - - diff --git a/topologie/api/urls.py b/topologie/api/urls.py index 4c5e859a..c5539418 100644 --- a/topologie/api/urls.py +++ b/topologie/api/urls.py @@ -33,13 +33,12 @@ urls_viewset = [ (r"topologie/dormitory", views.DormitoryViewSet, None), (r"topologie/switchport", views.SwitchPortViewSet, "switchport"), (r"topologie/portprofile", views.PortProfileViewSet, "portprofile"), - (r"topologie/room", views.RoomViewSet, None) + (r"topologie/room", views.RoomViewSet, None), ] urls_view = [ (r"topologie/switchs-ports-config", views.SwitchPortView), (r"topologie/switchs-role", views.RoleView), - # Deprecated (r"switchs/ports-config", views.SwitchPortView), (r"switchs/role", views.RoleView), diff --git a/topologie/api/views.py b/topologie/api/views.py index a84eda5f..41315fcc 100644 --- a/topologie/api/views.py +++ b/topologie/api/views.py @@ -19,48 +19,44 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from rest_framework import viewsets, generics +from rest_framework import generics, viewsets + +import machines.models as machines +import topologie.models as topologie from . import serializers -import topologie.models as topologie -import machines.models as machines class StackViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.Stack` objects. - """ + """Exposes list and details of `topologie.models.Stack` objects.""" queryset = topologie.Stack.objects.all() serializer_class = serializers.StackSerializer class AccessPointViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.AccessPoint` objects. - """ + """Exposes list and details of `topologie.models.AccessPoint` objects.""" queryset = topologie.AccessPoint.objects.all() serializer_class = serializers.AccessPointSerializer class SwitchViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.Switch` objects. - """ + """Exposes list and details of `topologie.models.Switch` objects.""" queryset = topologie.Switch.objects.all() serializer_class = serializers.SwitchSerializer class ServerViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.Server` objects. - """ + """Exposes list and details of `topologie.models.Server` objects.""" queryset = topologie.Server.objects.all() serializer_class = serializers.ServerSerializer class ModelSwitchViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.ModelSwitch` objects. - """ + """Exposes list and details of `topologie.models.ModelSwitch` objects.""" queryset = topologie.ModelSwitch.objects.all() serializer_class = serializers.ModelSwitchSerializer @@ -76,55 +72,51 @@ class ConstructorSwitchViewSet(viewsets.ReadOnlyModelViewSet): class SwitchBayViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.SwitchBay` objects. - """ + """Exposes list and details of `topologie.models.SwitchBay` objects.""" queryset = topologie.SwitchBay.objects.all() serializer_class = serializers.SwitchBaySerializer class BuildingViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.Building` objects. - """ + """Exposes list and details of `topologie.models.Building` objects.""" queryset = topologie.Building.objects.all() serializer_class = serializers.BuildingSerializer class SwitchPortViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.Port` objects. - """ + """Exposes list and details of `topologie.models.Port` objects.""" queryset = topologie.Port.objects.all() serializer_class = serializers.SwitchPortSerializer class PortProfileViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.PortProfile` objects. - """ + """Exposes list and details of `topologie.models.PortProfile` objects.""" queryset = topologie.PortProfile.objects.all() serializer_class = serializers.PortProfileSerializer class RoomViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.Room` objects. - """ + """Exposes list and details of `topologie.models.Room` objects.""" queryset = topologie.Room.objects.all() serializer_class = serializers.RoomSerializer + class DormitoryViewSet(viewsets.ReadOnlyModelViewSet): """Exposes list and details of `topologie.models.Dormitory` - objects. + objects. """ queryset = topologie.Dormitory.objects.all() serializer_class = serializers.DormitorySerializer + class PortProfileViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `topologie.models.PortProfile` objects. - """ + """Exposes list and details of `topologie.models.PortProfile` objects.""" queryset = topologie.PortProfile.objects.all() serializer_class = serializers.PortProfileSerializer @@ -149,8 +141,7 @@ class SwitchPortView(generics.ListAPIView): class RoleView(generics.ListAPIView): - """Output of roles for each server - """ + """Output of roles for each server""" queryset = machines.Role.objects.all().prefetch_related("servers") serializer_class = serializers.RoleSerializer diff --git a/topologie/apps.py b/topologie/apps.py index df131bc0..342857a0 100644 --- a/topologie/apps.py +++ b/topologie/apps.py @@ -8,4 +8,4 @@ from django.apps import AppConfig class TopologieConfig(AppConfig): """Configuration of topologie app.""" - name = "topologie" \ No newline at end of file + name = "topologie" diff --git a/topologie/forms.py b/topologie/forms.py index 0cbe1a5e..717cee2c 100644 --- a/topologie/forms.py +++ b/topologie/forms.py @@ -31,33 +31,19 @@ The forms are used to: from __future__ import unicode_literals from django import forms -from django.forms import ModelForm from django.db.models import Prefetch +from django.forms import ModelForm from django.utils.translation import ugettext_lazy as _ -from machines.models import Interface from machines.forms import EditMachineForm, NewMachineForm +from machines.models import Interface from re2o.mixins import FormRevMixin -from re2o.widgets import ( - AutocompleteModelWidget, - AutocompleteMultipleModelWidget, -) +from re2o.widgets import (AutocompleteModelWidget, + AutocompleteMultipleModelWidget) -from .models import ( - Port, - Switch, - Room, - Stack, - ModelSwitch, - ConstructorSwitch, - AccessPoint, - SwitchBay, - Building, - Dormitory, - PortProfile, - ModuleSwitch, - ModuleOnSwitch, -) +from .models import (AccessPoint, Building, ConstructorSwitch, Dormitory, + ModelSwitch, ModuleOnSwitch, ModuleSwitch, Port, + PortProfile, Room, Stack, Switch, SwitchBay) class PortForm(FormRevMixin, ModelForm): diff --git a/topologie/models.py b/topologie/models.py index b0d78249..9b2f22e7 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -36,19 +36,18 @@ from __future__ import unicode_literals import itertools -from django.db import models -from django.db.models.signals import post_save, post_delete -from django.utils.functional import cached_property from django.core.cache import cache -from django.dispatch import receiver from django.core.exceptions import ValidationError -from django.db import IntegrityError -from django.db import transaction +from django.db import IntegrityError, models, transaction +from django.db.models.signals import post_delete, post_save +from django.dispatch import receiver +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from reversion import revisions as reversion -from preferences.models import OptionalTopologie, RadiusKey, SwitchManagementCred -from machines.models import Machine, regen, Role, MachineType, Ipv6List +from machines.models import Ipv6List, Machine, MachineType, Role, regen +from preferences.models import (OptionalTopologie, RadiusKey, + SwitchManagementCred) from re2o.mixins import AclMixin, RevMixin @@ -364,7 +363,7 @@ class Switch(Machine): @cached_property def get_radius_servers_objects(self): """Return radius servers objects for Switchs provisioning, via REST API. - + Returns : Interfaces objects query_set for the Role type radius-server """ @@ -378,16 +377,17 @@ class Switch(Machine): def get_radius_servers(self): """Return radius servers string, ipv4 and ipv6 for Switchs provisioning, via REST API. - + Returns : Ip dict of interfaces for the Role type radius-server """ + def return_ips_dict(interfaces): return { "ipv4": [str(interface.ipv4) for interface in interfaces], - "ipv6": Ipv6List.objects.filter(interface__in=interfaces).filter(active=True).values_list( - "ipv6", flat=True - ), + "ipv6": Ipv6List.objects.filter(interface__in=interfaces) + .filter(active=True) + .values_list("ipv6", flat=True), } return return_ips_dict(self.get_radius_servers_objects) @@ -724,12 +724,7 @@ class Dormitory(AclMixin, RevMixin, models.Model): message. """ - return ( - True, - None, - None, - cls.objects.all() - ) + return (True, None, None, cls.objects.all()) def __str__(self): return self.name @@ -770,12 +765,7 @@ class Building(AclMixin, RevMixin, models.Model): message. """ - return ( - True, - None, - None, - cls.objects.all() - ) + return (True, None, None, cls.objects.all()) @cached_property def cached_name(self): @@ -820,7 +810,11 @@ class Port(AclMixin, RevMixin, models.Model): "machines.Interface", on_delete=models.SET_NULL, blank=True, null=True ) related = models.OneToOneField( - "self", null=True, blank=True, related_name="related_port", on_delete=models.SET_NULL + "self", + null=True, + blank=True, + related_name="related_port", + on_delete=models.SET_NULL, ) custom_profile = models.ForeignKey( "PortProfile", on_delete=models.PROTECT, blank=True, null=True @@ -966,12 +960,7 @@ class Room(AclMixin, RevMixin, models.Model): message. """ - return ( - True, - None, - None, - cls.objects.all() - ) + return (True, None, None, cls.objects.all()) def __str__(self): return self.building.cached_name + " " + self.name diff --git a/topologie/urls.py b/topologie/urls.py index 6e5640ee..d4718fd4 100644 --- a/topologie/urls.py +++ b/topologie/urls.py @@ -27,8 +27,7 @@ from __future__ import unicode_literals from django.urls import path -from . import views -from . import views_autocomplete +from . import views, views_autocomplete app_name = "topologie" @@ -37,9 +36,7 @@ urlpatterns = [ path("index_ap", views.index_ap, name="index-ap"), path("new_ap", views.new_ap, name="new-ap"), path("edit_ap/", views.edit_ap, name="edit-ap"), - path( - "create_ports/", views.create_ports, name="create-ports" - ), + path("create_ports/", views.create_ports, name="create-ports"), path("index_room", views.index_room, name="index-room"), path("new_room", views.new_room, name="new-room"), path("edit_room/", views.edit_room, name="edit-room"), @@ -157,9 +154,7 @@ urlpatterns = [ views.edit_module, name="edit-module", ), - path( - "del_module/", views.del_module, name="del-module" - ), + path("del_module/", views.del_module, name="del-module"), path("index_module", views.index_module, name="index-module"), path("add_module_on", views.add_module_on, name="add-module-on"), path( @@ -173,11 +168,39 @@ urlpatterns = [ name="del-module-on", ), ### Autocomplete Views - path('room-autocomplete', views_autocomplete.RoomAutocomplete.as_view(), name='room-autocomplete',), - path('building-autocomplete', views_autocomplete.BuildingAutocomplete.as_view(), name='building-autocomplete',), - path('dormitory-autocomplete', views_autocomplete.DormitoryAutocomplete.as_view(), name='dormitory-autocomplete',), - path('switch-autocomplete', views_autocomplete.SwitchAutocomplete.as_view(), name='switch-autocomplete',), - path('port-autocomplete', views_autocomplete.PortAutocomplete.as_view(), name='profile-autocomplete',), - path('portprofile-autocomplete', views_autocomplete.PortProfileAutocomplete.as_view(), name='portprofile-autocomplete',), - path('switchbay-autocomplete', views_autocomplete.SwitchBayAutocomplete.as_view(), name='switchbay-autocomplete',), + path( + "room-autocomplete", + views_autocomplete.RoomAutocomplete.as_view(), + name="room-autocomplete", + ), + path( + "building-autocomplete", + views_autocomplete.BuildingAutocomplete.as_view(), + name="building-autocomplete", + ), + path( + "dormitory-autocomplete", + views_autocomplete.DormitoryAutocomplete.as_view(), + name="dormitory-autocomplete", + ), + path( + "switch-autocomplete", + views_autocomplete.SwitchAutocomplete.as_view(), + name="switch-autocomplete", + ), + path( + "port-autocomplete", + views_autocomplete.PortAutocomplete.as_view(), + name="profile-autocomplete", + ), + path( + "portprofile-autocomplete", + views_autocomplete.PortProfileAutocomplete.as_view(), + name="portprofile-autocomplete", + ), + path( + "switchbay-autocomplete", + views_autocomplete.SwitchBayAutocomplete.as_view(), + name="switchbay-autocomplete", + ), ] diff --git a/topologie/views.py b/topologie/views.py index cddfa28e..fc25a3b5 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -34,70 +34,39 @@ They are used to create, edit and delete: """ from __future__ import unicode_literals -from django.urls import reverse -from django.shortcuts import render, redirect +import tempfile +from os.path import isfile +from subprocess import PIPE, Popen + from django.contrib import messages from django.contrib.auth.decorators import login_required -from django.db import IntegrityError -from django.db.models import ProtectedError, Prefetch from django.core.exceptions import ValidationError +from django.db import IntegrityError +from django.db.models import Prefetch, ProtectedError +from django.shortcuts import redirect, render from django.template import Context, Template, loader +from django.urls import reverse from django.utils.translation import ugettext as _ -import tempfile - -from users.views import form -from re2o.base import re2o_paginator, SortTable -from re2o.acl import can_create, can_edit, can_delete, can_view, can_view_all -from re2o.settings import MEDIA_ROOT -from machines.forms import ( - DomainForm, - EditInterfaceForm, - AddInterfaceForm, - EditOptionVlanForm, -) +from machines.forms import (AddInterfaceForm, DomainForm, EditInterfaceForm, + EditOptionVlanForm) from machines.models import Interface, Service_link, Vlan from preferences.models import AssoOption, GeneralOption +from re2o.acl import can_create, can_delete, can_edit, can_view, can_view_all +from re2o.base import SortTable, re2o_paginator +from re2o.settings import MEDIA_ROOT +from users.views import form -from .models import ( - Switch, - Port, - Room, - Stack, - ModelSwitch, - ConstructorSwitch, - AccessPoint, - SwitchBay, - Building, - Dormitory, - Server, - PortProfile, - ModuleSwitch, - ModuleOnSwitch, -) -from .forms import ( - EditPortForm, - NewSwitchForm, - EditSwitchForm, - AddPortForm, - EditRoomForm, - StackForm, - EditModelSwitchForm, - EditConstructorSwitchForm, - CreatePortsForm, - AddAccessPointForm, - EditAccessPointForm, - EditSwitchBayForm, - EditBuildingForm, - EditDormitoryForm, - EditPortProfileForm, - EditModuleForm, - EditSwitchModuleForm, -) - -from subprocess import Popen, PIPE - -from os.path import isfile +from .forms import (AddAccessPointForm, AddPortForm, CreatePortsForm, + EditAccessPointForm, EditBuildingForm, + EditConstructorSwitchForm, EditDormitoryForm, + EditModelSwitchForm, EditModuleForm, EditPortForm, + EditPortProfileForm, EditRoomForm, EditSwitchBayForm, + EditSwitchForm, EditSwitchModuleForm, NewSwitchForm, + StackForm) +from .models import (AccessPoint, Building, ConstructorSwitch, Dormitory, + ModelSwitch, ModuleOnSwitch, ModuleSwitch, Port, + PortProfile, Room, Server, Stack, Switch, SwitchBay) @login_required @@ -261,6 +230,7 @@ def index_switch_bay(request): }, ) + @login_required @can_view_all(Stack) def index_stack(request): @@ -276,6 +246,7 @@ def index_stack(request): }, ) + @login_required @can_view_all(Building) def index_building(request): @@ -295,6 +266,7 @@ def index_building(request): }, ) + @login_required @can_view_all(Dormitory) def index_dormitory(request): @@ -314,6 +286,7 @@ def index_dormitory(request): }, ) + @login_required @can_view_all(ModelSwitch, ConstructorSwitch) def index_model_switch(request): diff --git a/topologie/views_autocomplete.py b/topologie/views_autocomplete.py index 3eb58db4..767fb258 100644 --- a/topologie/views_autocomplete.py +++ b/topologie/views_autocomplete.py @@ -31,12 +31,13 @@ Here are defined the autocomplete class based view. """ from __future__ import unicode_literals -from django.db.models import Q, Value, CharField +from django.db.models import CharField, Q, Value from django.db.models.functions import Concat -from .models import Room, Dormitory, Building, Switch, PortProfile, Port, SwitchBay +from re2o.views import AutocompleteLoggedOutViewMixin, AutocompleteViewMixin -from re2o.views import AutocompleteViewMixin, AutocompleteLoggedOutViewMixin +from .models import (Building, Dormitory, Port, PortProfile, Room, Switch, + SwitchBay) class RoomAutocomplete(AutocompleteLoggedOutViewMixin): @@ -134,12 +135,8 @@ class SwitchBayAutocomplete(AutocompleteViewMixin): def filter_results(self): # See RoomAutocomplete.filter_results self.query_set = self.query_set.annotate( - full_name=Concat( - "building__name", Value(" "), "name" - ), - dorm_name=Concat( - "building__dormitory__name", Value(" "), "name" - ), + full_name=Concat("building__name", Value(" "), "name"), + dorm_name=Concat("building__dormitory__name", Value(" "), "name"), dorm_full_name=Concat( "building__dormitory__name", Value(" "), diff --git a/users/admin.py b/users/admin.py index d083a951..e394e35b 100644 --- a/users/admin.py +++ b/users/admin.py @@ -30,27 +30,13 @@ with AdherentAdmin, ClubAdmin and ServiceUserAdmin. from __future__ import unicode_literals from django.contrib import admin -from django.contrib.auth.models import Group from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.models import Group from reversion.admin import VersionAdmin -from .models import ( - User, - EMailAddress, - ServiceUser, - School, - ListRight, - ListShell, - Adherent, - Club, - Ban, - Whitelist, - Request, -) -from .forms import ( - UserAdminForm, - ServiceUserAdminForm, -) +from .forms import ServiceUserAdminForm, UserAdminForm +from .models import (Adherent, Ban, Club, EMailAddress, ListRight, ListShell, + Request, School, ServiceUser, User, Whitelist) class SchoolAdmin(VersionAdmin): @@ -167,7 +153,18 @@ class AdherentAdmin(VersionAdmin, BaseUserAdmin): (None, {"fields": ("pseudo",)}), ( "Personal info", - {"fields": ("surname", "name", "email", "school", "shell", "uid_number", "password1", "password2")}, + { + "fields": ( + "surname", + "name", + "email", + "school", + "shell", + "uid_number", + "password1", + "password2", + ) + }, ), ) # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin @@ -205,6 +202,7 @@ class ClubAdmin(VersionAdmin, BaseUserAdmin): Django ModelAdmin: Apply on django ModelAdmin """ + # The forms to add and change user instances add_form = UserAdminForm form = UserAdminForm @@ -223,7 +221,17 @@ class ClubAdmin(VersionAdmin, BaseUserAdmin): (None, {"fields": ("pseudo",)}), ( "Personal info", - {"fields": ("surname", "email", "school", "shell", "uid_number", "password1", "password2")}, + { + "fields": ( + "surname", + "email", + "school", + "shell", + "uid_number", + "password1", + "password2", + ) + }, ), ) @@ -269,7 +277,12 @@ class ServiceUserAdmin(VersionAdmin, BaseUserAdmin): # that reference specific fields on auth.User. list_display = ("pseudo", "access_group") list_filter = () - fieldsets = ((None, {"fields": ("pseudo", "access_group", "comment", "password1", "password2")}),) + fieldsets = ( + ( + None, + {"fields": ("pseudo", "access_group", "comment", "password1", "password2")}, + ), + ) # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin # overrides get_fieldsets to use this attribute when creating a user. add_fieldsets = ( diff --git a/users/api/serializers.py b/users/api/serializers.py index 4b77446a..048fdf52 100644 --- a/users/api/serializers.py +++ b/users/api/serializers.py @@ -22,11 +22,12 @@ from rest_framework import serializers import users.models as users -from api.serializers import NamespacedHRField, NamespacedHIField, NamespacedHMSerializer +from api.serializers import (NamespacedHIField, NamespacedHMSerializer, + NamespacedHRField) + class UserSerializer(NamespacedHMSerializer): - """Serialize `users.models.User` objects. - """ + """Serialize `users.models.User` objects.""" access = serializers.BooleanField(source="has_access") uid = serializers.IntegerField(source="uid_number") @@ -56,8 +57,7 @@ class UserSerializer(NamespacedHMSerializer): class ClubSerializer(NamespacedHMSerializer): - """Serialize `users.models.Club` objects. - """ + """Serialize `users.models.Club` objects.""" name = serializers.CharField(source="surname") access = serializers.BooleanField(source="has_access") @@ -91,8 +91,7 @@ class ClubSerializer(NamespacedHMSerializer): class AdherentSerializer(NamespacedHMSerializer): - """Serialize `users.models.Adherent` objects. - """ + """Serialize `users.models.Adherent` objects.""" access = serializers.BooleanField(source="has_access") uid = serializers.IntegerField(source="uid_number") @@ -135,8 +134,7 @@ class BasicUserSerializer(NamespacedHMSerializer): class ServiceUserSerializer(NamespacedHMSerializer): - """Serialize `users.models.ServiceUser` objects. - """ + """Serialize `users.models.ServiceUser` objects.""" class Meta: model = users.ServiceUser @@ -144,8 +142,7 @@ class ServiceUserSerializer(NamespacedHMSerializer): class SchoolSerializer(NamespacedHMSerializer): - """Serialize `users.models.School` objects. - """ + """Serialize `users.models.School` objects.""" class Meta: model = users.School @@ -153,8 +150,7 @@ class SchoolSerializer(NamespacedHMSerializer): class ListRightSerializer(NamespacedHMSerializer): - """Serialize `users.models.ListRight` objects. - """ + """Serialize `users.models.ListRight` objects.""" class Meta: model = users.ListRight @@ -162,8 +158,7 @@ class ListRightSerializer(NamespacedHMSerializer): class ShellSerializer(NamespacedHMSerializer): - """Serialize `users.models.ListShell` objects. - """ + """Serialize `users.models.ListShell` objects.""" class Meta: model = users.ListShell @@ -172,8 +167,7 @@ class ShellSerializer(NamespacedHMSerializer): class BanSerializer(NamespacedHMSerializer): - """Serialize `users.models.Ban` objects. - """ + """Serialize `users.models.Ban` objects.""" active = serializers.BooleanField(source="is_active") @@ -191,8 +185,7 @@ class BanSerializer(NamespacedHMSerializer): class WhitelistSerializer(NamespacedHMSerializer): - """Serialize `users.models.Whitelist` objects. - """ + """Serialize `users.models.Whitelist` objects.""" active = serializers.BooleanField(source="is_active") @@ -202,15 +195,14 @@ class WhitelistSerializer(NamespacedHMSerializer): class EMailAddressSerializer(NamespacedHMSerializer): - """Serialize `users.models.EMailAddress` objects. - """ + """Serialize `users.models.EMailAddress` objects.""" user = serializers.CharField(source="user.pseudo", read_only=True) class Meta: model = users.EMailAddress fields = ("user", "local_part", "complete_email_address", "api_url") - + class LocalEmailUsersSerializer(NamespacedHMSerializer): email_address = EMailAddressSerializer(read_only=True, many=True) @@ -226,16 +218,14 @@ class LocalEmailUsersSerializer(NamespacedHMSerializer): class MailingMemberSerializer(UserSerializer): - """Serialize the data about a mailing member. - """ + """Serialize the data about a mailing member.""" class Meta(UserSerializer.Meta): fields = ("name", "pseudo", "get_mail") class MailingSerializer(ClubSerializer): - """Serialize the data about a mailing. - """ + """Serialize the data about a mailing.""" members = MailingMemberSerializer(many=True) admins = MailingMemberSerializer(source="administrators", many=True) diff --git a/users/api/urls.py b/users/api/urls.py index 13598746..966f42ff 100644 --- a/users/api/urls.py +++ b/users/api/urls.py @@ -34,16 +34,15 @@ urls_viewset = [ (r"users/shell", views.ShellViewSet, "shell"), (r"users/ban", views.BanViewSet, None), (r"users/whitelist", views.WhitelistViewSet, None), - (r"users/emailaddress", views.EMailAddressViewSet, None) + (r"users/emailaddress", views.EMailAddressViewSet, None), ] urls_view = [ (r"users/localemail", views.LocalEmailUsersView), (r"users/mailing-standard", views.StandardMailingView), (r"users/mailing-club", views.ClubMailingView), - # Deprecated (r"localemail/users", views.LocalEmailUsersView), (r"mailing/standard", views.StandardMailingView), (r"mailing/club", views.ClubMailingView), -] \ No newline at end of file +] diff --git a/users/api/views.py b/users/api/views.py index e9c57b8a..b36ee633 100644 --- a/users/api/views.py +++ b/users/api/views.py @@ -19,29 +19,28 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -from rest_framework import viewsets, generics, views -from django.db.models import Q from django.contrib.auth.models import Group +from django.db.models import Q +from rest_framework import generics, views, viewsets -from . import serializers +import preferences.models as preferences +import users.models as users from api.pagination import PageSizedPagination from api.permissions import ACLPermission from re2o.utils import all_has_access -import users.models as users -import preferences.models as preferences + +from . import serializers class UserViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `users.models.Users` objects. - """ + """Exposes list and details of `users.models.Users` objects.""" queryset = users.User.objects.all() serializer_class = serializers.UserSerializer class HomeCreationViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes infos of `users.models.Users` objects to create homes. - """ + """Exposes infos of `users.models.Users` objects to create homes.""" queryset = users.User.objects.exclude( Q(state=users.User.STATE_DISABLED) @@ -66,72 +65,63 @@ class CriticalUserViewSet(viewsets.ReadOnlyModelViewSet): class ClubViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `users.models.Club` objects. - """ + """Exposes list and details of `users.models.Club` objects.""" queryset = users.Club.objects.all() serializer_class = serializers.ClubSerializer class AdherentViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `users.models.Adherent` objects. - """ + """Exposes list and details of `users.models.Adherent` objects.""" queryset = users.Adherent.objects.all() serializer_class = serializers.AdherentSerializer class ServiceUserViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `users.models.ServiceUser` objects. - """ + """Exposes list and details of `users.models.ServiceUser` objects.""" queryset = users.ServiceUser.objects.all() serializer_class = serializers.ServiceUserSerializer class SchoolViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `users.models.School` objects. - """ + """Exposes list and details of `users.models.School` objects.""" queryset = users.School.objects.all() serializer_class = serializers.SchoolSerializer class ListRightViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `users.models.ListRight` objects. - """ + """Exposes list and details of `users.models.ListRight` objects.""" queryset = users.ListRight.objects.all() serializer_class = serializers.ListRightSerializer class ShellViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `users.models.ListShell` objects. - """ + """Exposes list and details of `users.models.ListShell` objects.""" queryset = users.ListShell.objects.all() serializer_class = serializers.ShellSerializer class BanViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `users.models.Ban` objects. - """ + """Exposes list and details of `users.models.Ban` objects.""" queryset = users.Ban.objects.all() serializer_class = serializers.BanSerializer class WhitelistViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `users.models.Whitelist` objects. - """ + """Exposes list and details of `users.models.Whitelist` objects.""" queryset = users.Whitelist.objects.all() serializer_class = serializers.WhitelistSerializer class EMailAddressViewSet(viewsets.ReadOnlyModelViewSet): - """Exposes list and details of `users.models.EMailAddress` objects. - """ + """Exposes list and details of `users.models.EMailAddress` objects.""" serializer_class = serializers.EMailAddressSerializer queryset = users.EMailAddress.objects.none() @@ -144,8 +134,7 @@ class EMailAddressViewSet(viewsets.ReadOnlyModelViewSet): class LocalEmailUsersView(generics.ListAPIView): - """Exposes all the aliases of the users that activated the internal address - """ + """Exposes all the aliases of the users that activated the internal address""" serializer_class = serializers.LocalEmailUsersSerializer @@ -169,7 +158,7 @@ class StandardMailingView(views.APIView): adherents_data = serializers.MailingMemberSerializer( all_has_access(), many=True ).data - + data = [{"name": "adherents", "members": adherents_data}] groups = Group.objects.all() for group in groups: @@ -189,4 +178,4 @@ class ClubMailingView(generics.ListAPIView): """ queryset = users.Club.objects.all() - serializer_class = serializers.MailingSerializer \ No newline at end of file + serializer_class = serializers.MailingSerializer diff --git a/users/apps.py b/users/apps.py index 626c66e5..20c8a286 100644 --- a/users/apps.py +++ b/users/apps.py @@ -8,4 +8,4 @@ from django.apps import AppConfig class CoreConfig(AppConfig): """Configuration of users app.""" - name = "users" \ No newline at end of file + name = "users" diff --git a/users/forms.py b/users/forms.py index 172a8c54..a0bdb513 100644 --- a/users/forms.py +++ b/users/forms.py @@ -41,53 +41,35 @@ of each of the method. from __future__ import unicode_literals -from os import walk, path +from os import path, walk from django import forms -from django.forms import ModelForm, Form -from django.contrib.auth.forms import ReadOnlyPasswordHashField -from django.contrib.auth.password_validation import ( - validate_password, - password_validators_help_text_html, -) -from django.core.validators import MinLengthValidator from django.conf import settings +from django.contrib.auth.forms import ReadOnlyPasswordHashField +from django.contrib.auth.models import Group, Permission +from django.contrib.auth.password_validation import ( + password_validators_help_text_html, validate_password) +from django.core.validators import MinLengthValidator +from django.forms import Form, ModelForm from django.utils import timezone from django.utils.functional import lazy -from django.contrib.auth.models import Group, Permission -from django.utils.translation import ugettext_lazy as _ from django.utils.safestring import mark_safe +from django.utils.translation import ugettext_lazy as _ from machines.models import Interface, Machine, Nas -from topologie.models import Port -from preferences.models import OptionalUser -from re2o.utils import remove_user_room +from preferences.models import GeneralOption, OptionalUser from re2o.base import get_input_formats_help_text -from re2o.mixins import FormRevMixin -from re2o.widgets import ( - AutocompleteMultipleModelWidget, - AutocompleteModelWidget, -) from re2o.field_permissions import FieldPermissionFormMixin +from re2o.mixins import FormRevMixin +from re2o.utils import remove_user_room +from re2o.widgets import (AutocompleteModelWidget, + AutocompleteMultipleModelWidget) +from topologie.models import Port -from preferences.models import GeneralOption - +from .models import (Adherent, Ban, Club, EMailAddress, ListRight, ListShell, + School, ServiceUser, User, Whitelist) from .widgets import DateTimePicker -from .models import ( - User, - ServiceUser, - School, - ListRight, - Whitelist, - EMailAddress, - ListShell, - Ban, - Adherent, - Club, -) - - #### Django Admin Custom Views @@ -126,7 +108,7 @@ class UserAdminForm(FormRevMixin, forms.ModelForm): Parameters: self : Apply on a django Form UserCreationForm instance - + Returns: password2 (string): The password2 value if all tests returned True """ @@ -182,7 +164,7 @@ class ServiceUserAdminForm(FormRevMixin, forms.ModelForm): Parameters: self : Apply on a django Form UserCreationForm instance - + Returns: password2 (string): The password2 value if all tests returned True """ @@ -194,7 +176,7 @@ class ServiceUserAdminForm(FormRevMixin, forms.ModelForm): def save(self, commit=True): """Save function. Call standard "set_password" django function, - from provided value for new password, for making hash. + from provided value for new password, for making hash. Parameters: self : Apply on a django Form ServiceUserAdminForm instance @@ -213,10 +195,10 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): """Django form for changing password, check if 2 passwords are the same, and validate password for django base password validators provided in settings_local. - + Parameters: DjangoForm : Inherit from basic django form - + """ selfpasswd = forms.CharField( @@ -238,11 +220,11 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): def clean_passwd2(self): """Clean password 2, check if passwd1 and 2 values match, and - apply django validator with validate_password function. - + apply django validator with validate_password function. + Parameters: self : Apply on a django Form PassForm instance - + Returns: password2 (string): The password2 value if all tests returned True """ @@ -278,7 +260,7 @@ class PassForm(FormRevMixin, FieldPermissionFormMixin, forms.ModelForm): class ResetPasswordForm(forms.Form): - """A form for asking to reset password. + """A form for asking to reset password. Parameters: DjangoForm : Inherit from basic django form @@ -368,10 +350,10 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): def clean_telephone(self): """Clean telephone, check if telephone is made mandatory, and raise error if not provided - + Parameters: self : Apply on a django Form AdherentForm instance - + Returns: telephone (string): The telephone string if clean is True """ @@ -395,10 +377,10 @@ class AdherentForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): """Clean room, based on room policy provided by preferences. If needed, call remove_user_room to make the room empty before saving self.instance into that room. - + Parameters: self : Apply on a django Form AdherentForm instance - + Returns: room (string): The room instance """ @@ -505,11 +487,11 @@ class AdherentCreationForm(AdherentForm): def clean_password2(self): """Clean password 2, check if passwd1 and 2 values match, and - apply django validator with validate_password function. + apply django validator with validate_password function. Parameters: self : Apply on a django Form AdherentCreationForm instance - + Returns: password2 (string): The password2 value if all tests returned True """ @@ -526,7 +508,7 @@ class AdherentCreationForm(AdherentForm): return password2 def save(self, commit=True): - """Save function. If password has been set during creation, + """Save function. If password has been set during creation, call standard "set_password" django function from provided value for new password, for making hash. @@ -588,7 +570,7 @@ class AdherentEditForm(AdherentForm): class ClubForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): """ClubForm. For editing club by himself or another user. Labels are provided for - help purposes. Add some instructions, and validation, fields depends + help purposes. Add some instructions, and validation, fields depends on editing user rights. Parameters: @@ -631,10 +613,10 @@ class ClubForm(FormRevMixin, FieldPermissionFormMixin, ModelForm): def clean_telephone(self): """Clean telephone, check if telephone is made mandatory, and raise error if not provided - + Parameters: self : Apply on a django Form ClubForm instance - + Returns: telephone (string): The telephone string if clean is True """ @@ -671,7 +653,7 @@ class ClubAdminandMembersForm(FormRevMixin, ModelForm): class PasswordForm(FormRevMixin, ModelForm): """PasswordForm. Do not use directly in views without extra validations. - + Parameters: DjangoForm : Inherit from basic django form """ @@ -688,7 +670,7 @@ class PasswordForm(FormRevMixin, ModelForm): class ServiceUserForm(FormRevMixin, ModelForm): """ServiceUserForm, used for creating a service user, require a password and set it. - + Parameters: DjangoForm : Inherit from basic django form """ @@ -710,7 +692,7 @@ class ServiceUserForm(FormRevMixin, ModelForm): super(ServiceUserForm, self).__init__(*args, prefix=prefix, **kwargs) def save(self, commit=True): - """Save function. If password has been changed and provided, + """Save function. If password has been changed and provided, call standard "set_password" django function from provided value for new password, for making hash. @@ -1044,10 +1026,10 @@ class InitialRegisterForm(forms.Form): def clean_register_room(self): """Clean room, call remove_user_room to make the room empty before saving self.instance into that room. - + Parameters: self : Apply on a django Form InitialRegisterForm instance - + """ if self.cleaned_data["register_room"]: if self.user.is_class_adherent: @@ -1065,7 +1047,7 @@ class InitialRegisterForm(forms.Form): Parameters: self : Apply on a django Form InitialRegisterForm instance - + """ if self.cleaned_data["register_machine"]: if self.mac_address and self.nas_type: @@ -1073,8 +1055,7 @@ class InitialRegisterForm(forms.Form): class ThemeForm(FormRevMixin, forms.Form): - """Form to change the theme of a user. - """ + """Form to change the theme of a user.""" theme = forms.ChoiceField(widget=forms.Select()) diff --git a/users/management/commands/anonymise.py b/users/management/commands/anonymise.py index 700905b9..29617fc1 100644 --- a/users/management/commands/anonymise.py +++ b/users/management/commands/anonymise.py @@ -1,16 +1,17 @@ -from django.core.management.base import BaseCommand -from users.models import User, School, Adherent, Club -from machines.models import Domain, Machine -from reversion.models import Revision -from django.db.models import F, Value -from django.db.models import Q -from django.db.models.functions import Concat - -from re2o.login import hashNT, makeSecret - -import os, random, string +import os +import random +import string from random import randint +from django.core.management.base import BaseCommand +from django.db.models import F, Q, Value +from django.db.models.functions import Concat +from reversion.models import Revision + +from machines.models import Domain, Machine +from re2o.login import hashNT, makeSecret +from users.models import Adherent, Club, School, User + class Command(BaseCommand): help = "Anonymise the data in the database in order to use them on critical servers (dev, personal...). Every information will be overwritten using non-personal information. This script must follow any modification of the database.\nOptional argument: {id|id|id|...} to exclude users from anonymisation." diff --git a/users/management/commands/chgpass.py b/users/management/commands/chgpass.py index 5de9aa8d..d7f868eb 100644 --- a/users/management/commands/chgpass.py +++ b/users/management/commands/chgpass.py @@ -23,8 +23,9 @@ import os import pwd from django.core.management.base import BaseCommand, CommandError + +from re2o.script_utils import form_cli, get_system_user, get_user from users.forms import PassForm -from re2o.script_utils import get_user, get_system_user, form_cli class Command(BaseCommand): @@ -46,6 +47,4 @@ class Command(BaseCommand): self.stdout.write("Password change of %s" % target_user.pseudo) - form_cli( - PassForm, current_user, "Password change", instance=target_user - ) + form_cli(PassForm, current_user, "Password change", instance=target_user) diff --git a/users/management/commands/chsh.py b/users/management/commands/chsh.py index 0b815543..01fa3de6 100644 --- a/users/management/commands/chsh.py +++ b/users/management/commands/chsh.py @@ -20,15 +20,15 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import os -import sys import pwd +import sys from django.core.management.base import BaseCommand, CommandError from django.db import transaction from reversion import revisions as reversion -from users.models import User, ListShell -from re2o.script_utils import get_user, get_system_user +from re2o.script_utils import get_system_user, get_user +from users.models import ListShell, User class Command(BaseCommand): diff --git a/users/management/commands/clean_notyetactive.py b/users/management/commands/clean_notyetactive.py index 14f7c826..0e771ea8 100644 --- a/users/management/commands/clean_notyetactive.py +++ b/users/management/commands/clean_notyetactive.py @@ -16,16 +16,16 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -from django.core.management.base import BaseCommand, CommandError -from django.db.models import Q - -from users.models import User -from cotisations.models import Facture -from preferences.models import OptionalUser from datetime import timedelta +from django.core.management.base import BaseCommand, CommandError +from django.db.models import Q from django.utils import timezone +from cotisations.models import Facture +from preferences.models import OptionalUser +from users.models import User + class Command(BaseCommand): help = "Delete non members users (not yet active)." diff --git a/users/management/commands/derniere_connexion.py b/users/management/commands/derniere_connexion.py index 9d753809..70de99f5 100644 --- a/users/management/commands/derniere_connexion.py +++ b/users/management/commands/derniere_connexion.py @@ -20,8 +20,8 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -import sys import re +import sys from datetime import datetime from django.core.management.base import BaseCommand, CommandError diff --git a/users/management/commands/disable_emailnotyetconfirmed.py b/users/management/commands/disable_emailnotyetconfirmed.py index 85b12699..4d50d1fa 100644 --- a/users/management/commands/disable_emailnotyetconfirmed.py +++ b/users/management/commands/disable_emailnotyetconfirmed.py @@ -14,15 +14,15 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -from django.core.management.base import BaseCommand, CommandError - -from users.models import User -from cotisations.models import Facture -from preferences.models import OptionalUser from datetime import timedelta +from django.core.management.base import BaseCommand, CommandError from django.utils import timezone +from cotisations.models import Facture +from preferences.models import OptionalUser +from users.models import User + class Command(BaseCommand): help = "Disable users who haven't confirmed their email." diff --git a/users/models.py b/users/models.py index 5256d7c5..ca8f3084 100755 --- a/users/models.py +++ b/users/models.py @@ -44,55 +44,43 @@ Here are defined the following django models : from __future__ import unicode_literals -import re -import uuid import datetime +import re import sys +import traceback +import uuid +from datetime import timedelta +from io import BytesIO -from django.db import models -from django.db.models import Q from django import forms -from django.forms import ValidationError -from django.db.models.signals import post_save, post_delete, m2m_changed +from django.contrib.auth.models import (AbstractBaseUser, BaseUserManager, + Group, PermissionsMixin) +from django.core.files.uploadedfile import InMemoryUploadedFile +from django.core.validators import RegexValidator +from django.db import models, transaction +from django.db.models import Q +from django.db.models.signals import m2m_changed, post_delete, post_save from django.dispatch import receiver -from django.utils.functional import cached_property +from django.forms import ValidationError from django.template import loader from django.urls import reverse -from django.db import transaction from django.utils import timezone -from datetime import timedelta -from django.contrib.auth.models import ( - AbstractBaseUser, - BaseUserManager, - PermissionsMixin, - Group, -) -from django.core.validators import RegexValidator -import traceback +from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from django.core.files.uploadedfile import InMemoryUploadedFile - +from PIL import Image from reversion import revisions as reversion - -from re2o.settings import LDAP, GID_RANGES, UID_RANGES -from re2o.field_permissions import FieldPermissionModelMixin -from re2o.mixins import AclMixin, RevMixin -from re2o.base import smtp_check -from re2o.mail_utils import send_mail - from cotisations.models import Cotisation, Facture, Paiement, Vente from machines.models import Domain, Interface, Machine, regen -from preferences.models import GeneralOption, AssoOption, OptionalUser -from preferences.models import OptionalMachine, MailMessageOption - +from preferences.models import (AssoOption, GeneralOption, MailMessageOption, + OptionalMachine, OptionalUser) +from re2o.base import smtp_check +from re2o.field_permissions import FieldPermissionModelMixin +from re2o.mail_utils import send_mail +from re2o.mixins import AclMixin, RevMixin +from re2o.settings import GID_RANGES, LDAP, UID_RANGES from users import signals -from PIL import Image -from io import BytesIO -import sys - - # General utilities @@ -121,7 +109,7 @@ def linux_user_validator(login): """ if not linux_user_check(login): raise forms.ValidationError( - _("The username \"%(label)s\" contains forbidden characters."), + _('The username "%(label)s" contains forbidden characters.'), params={"label": login}, ) @@ -259,31 +247,28 @@ class User( ), ) local_email_enabled = models.BooleanField( - default=False, - help_text=_("Enable the local email account.") + default=False, help_text=_("Enable the local email account.") ) school = models.ForeignKey( "School", on_delete=models.PROTECT, null=True, blank=True, - help_text=_("Education institute.") + help_text=_("Education institute."), ) shell = models.ForeignKey( "ListShell", on_delete=models.PROTECT, null=True, blank=True, - help_text=_("Unix shell.") + help_text=_("Unix shell."), ) comment = models.CharField( help_text=_("Comment, school year."), max_length=255, blank=True ) pwd_ntlm = models.CharField(max_length=255) state = models.IntegerField( - choices=STATES, - default=STATE_NOT_YET_ACTIVE, - help_text=_("Account state.") + choices=STATES, default=STATE_NOT_YET_ACTIVE, help_text=_("Account state.") ) email_state = models.IntegerField(choices=EMAIL_STATES, default=EMAIL_STATE_PENDING) registered = models.DateTimeField(auto_now_add=True) @@ -293,7 +278,7 @@ class User( unique=True, blank=True, null=True, - help_text=_("Optionnal legacy uid, for import and transition purpose") + help_text=_("Optionnal legacy uid, for import and transition purpose"), ) shortcuts_enabled = models.BooleanField( verbose_name=_("enable shortcuts on Re2o website"), default=True @@ -596,7 +581,10 @@ class User( Returns: shadow_expire (int) : Shadow expire value. """ - if self.state == self.STATE_DISABLED or self.email_state == self.EMAIL_STATE_UNVERIFIED: + if ( + self.state == self.STATE_DISABLED + or self.email_state == self.EMAIL_STATE_UNVERIFIED + ): return str(0) else: return None @@ -675,14 +663,11 @@ class User( Returns: end_adhesion (date) : Date of the end of the membership. """ - date_max = ( - Cotisation.objects.filter( - vente__in=Vente.objects.filter( - facture__in=Facture.objects.filter(user=self).exclude(valid=False) - ) + date_max = Cotisation.objects.filter( + vente__in=Vente.objects.filter( + facture__in=Facture.objects.filter(user=self).exclude(valid=False) ) - .aggregate(models.Max("date_end_memb"))["date_end_memb__max"] - ) + ).aggregate(models.Max("date_end_memb"))["date_end_memb__max"] return date_max def end_connexion(self): @@ -695,14 +680,11 @@ class User( Returns: end_adhesion (date) : Date of the end of the connection subscription. """ - date_max = ( - Cotisation.objects.filter( - vente__in=Vente.objects.filter( - facture__in=Facture.objects.filter(user=self).exclude(valid=False) - ) + date_max = Cotisation.objects.filter( + vente__in=Vente.objects.filter( + facture__in=Facture.objects.filter(user=self).exclude(valid=False) ) - .aggregate(models.Max("date_end_con"))["date_end_con__max"] - ) + ).aggregate(models.Max("date_end_con"))["date_end_con__max"] return date_max def is_adherent(self): @@ -722,8 +704,8 @@ class User( return False else: return True - # it looks wrong, we should check if there is a cotisation where - # were date_start_memb < timezone.now() < date_end_memb, + # it looks wrong, we should check if there is a cotisation where + # were date_start_memb < timezone.now() < date_end_memb, # in case the user purshased a cotisation starting in the futur # somehow @@ -745,8 +727,8 @@ class User( return False else: return self.is_adherent() - # it looks wrong, we should check if there is a cotisation where - # were date_start_con < timezone.now() < date_end_con, + # it looks wrong, we should check if there is a cotisation where + # were date_start_con < timezone.now() < date_end_con, # in case the user purshased a cotisation starting in the futur # somehow @@ -910,10 +892,21 @@ class User( """ if self.state == self.STATE_NOT_YET_ACTIVE: # Look for ventes with non 0 subscription duration in the invoices set - not_zero = self.facture_set.filter(valid=True).exclude(Q(vente__duration_membership=0)).exists() - days_not_zero = self.facture_set.filter(valid=True).exclude(Q(vente__duration_days_membership=0)).exists() - if(not_zero or days_not_zero\ - or OptionalUser.get_cached_value("all_users_active")): + not_zero = ( + self.facture_set.filter(valid=True) + .exclude(Q(vente__duration_membership=0)) + .exists() + ) + days_not_zero = ( + self.facture_set.filter(valid=True) + .exclude(Q(vente__duration_days_membership=0)) + .exists() + ) + if ( + not_zero + or days_not_zero + or OptionalUser.get_cached_value("all_users_active") + ): self.state = self.STATE_ACTIVE self.save() if self.state == self.STATE_ARCHIVE or self.state == self.STATE_FULL_ARCHIVE: @@ -1493,7 +1486,7 @@ class User( False, _( "Impossible to edit the organisation's" - " user without the \"change_all_users\" right." + ' user without the "change_all_users" right.' ), ("users.change_all_users",), ) @@ -1547,13 +1540,13 @@ class User( ) def check_selfpasswd(self, user_request, *_args, **_kwargs): - """ Returns (True, None, None) if user_request is self, else returns + """Returns (True, None, None) if user_request is self, else returns (False, None, None) """ return user_request == self, None, None def can_change_room(self, user_request, *_args, **_kwargs): - """ Check if a user can change a room + """Check if a user can change a room :param user_request: The user who request :returns: a message and a boolean which is True if the user has @@ -1562,7 +1555,8 @@ class User( if not ( ( self.pk == user_request.pk - and OptionalUser.get_cached_value("self_room_policy") != OptionalUser.DISABLED + and OptionalUser.get_cached_value("self_room_policy") + != OptionalUser.DISABLED ) or user_request.has_perm("users.change_user") ): @@ -1576,7 +1570,7 @@ class User( @staticmethod def can_change_state(user_request, *_args, **_kwargs): - """ Check if a user can change a state + """Check if a user can change a state :param user_request: The user who request :returns: a message and a boolean which is True if the user has @@ -1590,7 +1584,7 @@ class User( ) def can_change_shell(self, user_request, *_args, **_kwargs): - """ Check if a user can change a shell + """Check if a user can change a shell :param user_request: The user who request :returns: a message and a boolean which is True if the user has @@ -1612,7 +1606,7 @@ class User( return True, None, None def can_change_pseudo(self, user_request, *_args, **_kwargs): - """ Check if a user can change a pseudo + """Check if a user can change a pseudo :param user_request: The user who request :returns: a message and a boolean which is True if the user has @@ -1636,7 +1630,7 @@ class User( @staticmethod def can_change_local_email_redirect(user_request, *_args, **_kwargs): - """ Check if a user can change local_email_redirect. + """Check if a user can change local_email_redirect. :param user_request: The user who request :returns: a message and a boolean which is True if the user has @@ -1651,7 +1645,7 @@ class User( @staticmethod def can_change_local_email_enabled(user_request, *_args, **_kwargs): - """ Check if a user can change internal address. + """Check if a user can change internal address. :param user_request: The user who request :returns: a message and a boolean which is True if the user has @@ -1666,7 +1660,7 @@ class User( @staticmethod def can_change_force(user_request, *_args, **_kwargs): - """ Check if a user can change a force + """Check if a user can change a force :param user_request: The user who request :returns: a message and a boolean which is True if the user has @@ -1681,7 +1675,7 @@ class User( @staticmethod def can_change_groups(user_request, *_args, **_kwargs): - """ Check if a user can change a group + """Check if a user can change a group :param user_request: The user who request :returns: a message and a boolean which is True if the user has @@ -1698,7 +1692,7 @@ class User( @staticmethod def can_change_is_superuser(user_request, *_args, **_kwargs): - """ Check if an user can change a is_superuser flag + """Check if an user can change a is_superuser flag :param user_request: The user who request :returns: a message and a boolean which is True if permission is granted. @@ -1707,7 +1701,7 @@ class User( can = user_request.is_superuser return ( can, - _("\"superuser\" right required to edit the superuser flag.") + _('"superuser" right required to edit the superuser flag.') if not can else None, [], @@ -1826,9 +1820,7 @@ class User( """ is_created = not self.pk if not self.email and (self.__original_email or is_created): - raise forms.ValidationError( - _("Email field cannot be empty.") - ) + raise forms.ValidationError(_("Email field cannot be empty.")) self.email = self.email.lower() @@ -1937,9 +1929,7 @@ class Adherent(User): """ if not user_request.is_authenticated: - if not OptionalUser.get_cached_value( - "self_adhesion" - ): + if not OptionalUser.get_cached_value("self_adhesion"): return False, _("Self registration is disabled."), None else: return True, None, None @@ -1969,12 +1959,7 @@ class Adherent(User): """ can, _message, _group = Club.can_view_all(user_request) if user_request.has_perm("users.view_user") or can: - return ( - True, - None, - None, - cls.objects.all() - ) + return (True, None, None, cls.objects.all()) else: return ( True, @@ -1997,7 +1982,7 @@ class Adherent(User): class Club(User): - """ A class representing a club (it is considered as a user + """A class representing a club (it is considered as a user with special informations) Attributes: @@ -2098,7 +2083,13 @@ def user_post_save(**kwargs): user.notif_inscription(user.request) user.set_active() user.state_sync() - signals.synchronise.send(sender=User, instance=user, base=True, access_refresh=True, mac_refresh=False, group_refresh=True + signals.synchronise.send( + sender=User, + instance=user, + base=True, + access_refresh=True, + mac_refresh=False, + group_refresh=True, ) regen("mailing") @@ -2112,7 +2103,13 @@ def user_group_relation_changed(**kwargs): action = kwargs["action"] if action in ("post_add", "post_remove", "post_clear"): user = kwargs["instance"] - signals.synchronise.send(sender=User, instance=user, base=False, access_refresh=False, mac_refresh=False, group_refresh=True + signals.synchronise.send( + sender=User, + instance=user, + base=False, + access_refresh=False, + mac_refresh=False, + group_refresh=True, ) @@ -2228,19 +2225,14 @@ class School(RevMixin, AclMixin, models.Model): message. """ - return ( - True, - None, - None, - cls.objects.all() - ) + return (True, None, None, cls.objects.all()) def __str__(self): return self.name class ListRight(RevMixin, AclMixin, Group): - """ A class representing a listright, inherit from basic django Group object. + """A class representing a listright, inherit from basic django Group object. Each listrights/groups gathers several users, and can have individuals django rights, like can_view, can_edit, etc. Moreover, a ListRight is also a standard unix group, usefull for creating linux @@ -2332,19 +2324,14 @@ class ListShell(RevMixin, AclMixin, models.Model): message. """ - return ( - True, - None, - None, - cls.objects.all() - ) + return (True, None, None, cls.objects.all()) def __str__(self): return self.shell class Ban(RevMixin, AclMixin, models.Model): - """ A class representing a ban, which cuts internet access, + """A class representing a ban, which cuts internet access, as a sanction. Attributes: @@ -2447,7 +2434,9 @@ def ban_post_save(**kwargs): ban = kwargs["instance"] is_created = kwargs["created"] user = ban.user - signals.synchronise.send(sender=User, instance=user, base=False, access_refresh=True, mac_refresh=False) + signals.synchronise.send( + sender=User, instance=user, base=False, access_refresh=True, mac_refresh=False + ) regen("mailing") if is_created: ban.notif_ban(ban.request) @@ -2465,14 +2454,16 @@ def ban_post_delete(**kwargs): """ user = kwargs["instance"].user - signals.synchronise.send(sender=User, instance=user, base=False, access_refresh=True, mac_refresh=False) + signals.synchronise.send( + sender=User, instance=user, base=False, access_refresh=True, mac_refresh=False + ) regen("mailing") regen("dhcp") regen("mac_ip_list") class Whitelist(RevMixin, AclMixin, models.Model): - """ A class representing a whitelist, which gives a free internet + """A class representing a whitelist, which gives a free internet access to a user for special reason. Is overrided by a ban object. @@ -2536,7 +2527,9 @@ def whitelist_post_save(**kwargs): """ whitelist = kwargs["instance"] user = whitelist.user - signals.synchronise.send(sender=User, instance=user, base=False, access_refresh=True, mac_refresh=False) + signals.synchronise.send( + sender=User, instance=user, base=False, access_refresh=True, mac_refresh=False + ) is_created = kwargs["created"] regen("mailing") if is_created: @@ -2554,14 +2547,16 @@ def whitelist_post_delete(**kwargs): """ user = kwargs["instance"].user - signals.synchronise.send(sender=User, instance=user, base=False, access_refresh=True, mac_refresh=False) + signals.synchronise.send( + sender=User, instance=user, base=False, access_refresh=True, mac_refresh=False + ) regen("mailing") regen("dhcp") regen("mac_ip_list") class Request(models.Model): - """ A class representing for user's request of reset password by email, or + """A class representing for user's request of reset password by email, or confirm a new email address, with a link. Attributes: @@ -2594,7 +2589,7 @@ class Request(models.Model): class EMailAddress(RevMixin, AclMixin, models.Model): - """ A class representing an EMailAddress, for local emailaccounts + """A class representing an EMailAddress, for local emailaccounts support. Each emailaddress belongs to a user. Attributes: diff --git a/users/signals.py b/users/signals.py index 7336e5fc..3b46ad1d 100644 --- a/users/signals.py +++ b/users/signals.py @@ -27,7 +27,15 @@ remove an object from optionnal authentication backends, e.g. LDAP. import django.dispatch -synchronise = django.dispatch.Signal(providing_args=["sender", "instance", "base", "access_refresh", "mac_refresh", "group_refresh"]) +synchronise = django.dispatch.Signal( + providing_args=[ + "sender", + "instance", + "base", + "access_refresh", + "mac_refresh", + "group_refresh", + ] +) remove = django.dispatch.Signal(providing_args=["sender", "instance"]) remove_mass = django.dispatch.Signal(providing_args=["sender", "queryset"]) - diff --git a/users/test_models.py b/users/test_models.py index bb69cb79..665ff6c4 100644 --- a/users/test_models.py +++ b/users/test_models.py @@ -1,10 +1,10 @@ -from django.test import TestCase - import datetime + +from django.test import TestCase from django.utils import timezone +from cotisations.models import Facture, Paiement, Vente from users.models import User -from cotisations.models import Vente, Facture, Paiement class UserModelTests(TestCase): diff --git a/users/tests.py b/users/tests.py index 4884f971..805fbaa8 100644 --- a/users/tests.py +++ b/users/tests.py @@ -25,11 +25,11 @@ The tests for the Users module. import os.path -from django.test import TestCase -from django.conf import settings -from . import models - import volatildap +from django.conf import settings +from django.test import TestCase + +from . import models class SchoolTestCase(TestCase): diff --git a/users/urls.py b/users/urls.py index 3dc1d5d8..be866470 100644 --- a/users/urls.py +++ b/users/urls.py @@ -30,8 +30,7 @@ from __future__ import unicode_literals from django.urls import path, re_path -from . import views -from . import views_autocomplete +from . import views, views_autocomplete app_name = "users" @@ -47,15 +46,17 @@ urlpatterns = [ path("state/", views.state, name="state"), path("groups/", views.groups, name="groups"), path("password/", views.password, name="password"), - path("confirm_email/", views.resend_confirmation_email, name="resend-confirmation-email"), + path( + "confirm_email/", + views.resend_confirmation_email, + name="resend-confirmation-email", + ), path( "del_group//", views.del_group, name="del-group", ), - path( - "del_superuser/", views.del_superuser, name="del-superuser" - ), + path("del_superuser/", views.del_superuser, name="del-superuser"), path("new_serviceuser", views.new_serviceuser, name="new-serviceuser"), path( "edit_serviceuser/", @@ -70,9 +71,7 @@ urlpatterns = [ path("add_ban/", views.add_ban, name="add-ban"), path("edit_ban/", views.edit_ban, name="edit-ban"), path("del-ban/", views.del_ban, name="del-ban"), - path( - "add_whitelist/", views.add_whitelist, name="add-whitelist" - ), + path("add_whitelist/", views.add_whitelist, name="add-whitelist"), path( "edit_whitelist/", views.edit_whitelist, @@ -132,9 +131,29 @@ urlpatterns = [ path("initial_register", views.initial_register, name="initial-register"), path("edit_theme/", views.edit_theme, name="edit-theme"), ### Autocomplete Views - path('user-autocomplete', views_autocomplete.UserAutocomplete.as_view(), name='user-autocomplete',), - path('adherent-autocomplete', views_autocomplete.AdherentAutocomplete.as_view(), name='adherent-autocomplete',), - path('club-autocomplete', views_autocomplete.ClubAutocomplete.as_view(), name='club-autocomplete',), - path('school-autocomplete', views_autocomplete.SchoolAutocomplete.as_view(), name='school-autocomplete',), - path('shell-autocomplete', views_autocomplete.ShellAutocomplete.as_view(), name='shell-autocomplete',), + path( + "user-autocomplete", + views_autocomplete.UserAutocomplete.as_view(), + name="user-autocomplete", + ), + path( + "adherent-autocomplete", + views_autocomplete.AdherentAutocomplete.as_view(), + name="adherent-autocomplete", + ), + path( + "club-autocomplete", + views_autocomplete.ClubAutocomplete.as_view(), + name="club-autocomplete", + ), + path( + "school-autocomplete", + views_autocomplete.SchoolAutocomplete.as_view(), + name="school-autocomplete", + ), + path( + "shell-autocomplete", + views_autocomplete.ShellAutocomplete.as_view(), + name="shell-autocomplete", + ), ] diff --git a/users/views.py b/users/views.py index 0b671aba..54a0f34e 100644 --- a/users/views.py +++ b/users/views.py @@ -55,83 +55,46 @@ without code duplication. from __future__ import unicode_literals -from django.urls import reverse -from django.shortcuts import get_object_or_404, render, redirect +import os +from importlib import import_module + +from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required -from django.db.models import ProtectedError, Count, Max -from django.utils import timezone from django.db import transaction -from django.http import HttpResponse -from django.http import HttpResponseRedirect -from django.views.decorators.csrf import csrf_exempt -from django.utils.translation import ugettext as _ +from django.db.models import Count, Max, ProtectedError +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import get_object_or_404, redirect, render from django.template import loader - +from django.urls import reverse +from django.utils import timezone +from django.utils.translation import ugettext as _ +from django.views.decorators.csrf import csrf_exempt from rest_framework.renderers import JSONRenderer from reversion import revisions as reversion from cotisations.models import Facture, Paiement -from machines.models import Machine - -from preferences.models import OptionalUser, GeneralOption, AssoOption -from importlib import import_module -from django.conf import settings -from re2o.settings import LOCAL_APPS, OPTIONNAL_APPS_RE2O -from re2o.views import form -from re2o.utils import all_has_access, permission_tree -from re2o.base import re2o_paginator, SortTable -from re2o.acl import ( - can_create, - can_edit, - can_delete_set, - can_delete, - can_view, - can_view_all, - can_change, -) from cotisations.utils import find_payment_method +from machines.models import Machine +from preferences.models import AssoOption, GeneralOption, OptionalUser +from re2o.acl import (can_change, can_create, can_delete, can_delete_set, + can_edit, can_view, can_view_all) +from re2o.base import SortTable, re2o_paginator +from re2o.settings import LOCAL_APPS, OPTIONNAL_APPS_RE2O +from re2o.utils import all_has_access, permission_tree +from re2o.views import form from topologie.models import Port -from .models import ( - User, - Ban, - Whitelist, - School, - ListRight, - Request, - ServiceUser, - Adherent, - Club, - ListShell, - EMailAddress, -) -from .forms import ( - BanForm, - WhitelistForm, - EMailAddressForm, - EmailSettingsForm, - DelSchoolForm, - DelListRightForm, - NewListRightForm, - StateForm, - SchoolForm, - ShellForm, - EditServiceUserForm, - ServiceUserForm, - ListRightForm, - AdherentCreationForm, - AdherentEditForm, - ClubForm, - MassArchiveForm, - PassForm, - ResetPasswordForm, - ClubAdminandMembersForm, - GroupForm, - InitialRegisterForm, - ThemeForm -) -import os +from .forms import (AdherentCreationForm, AdherentEditForm, BanForm, + ClubAdminandMembersForm, ClubForm, DelListRightForm, + DelSchoolForm, EditServiceUserForm, EMailAddressForm, + EmailSettingsForm, GroupForm, InitialRegisterForm, + ListRightForm, MassArchiveForm, NewListRightForm, PassForm, + ResetPasswordForm, SchoolForm, ServiceUserForm, ShellForm, + StateForm, ThemeForm, WhitelistForm) +from .models import (Adherent, Ban, Club, EMailAddress, ListRight, ListShell, + Request, School, ServiceUser, User, Whitelist) + @can_create(Adherent) def new_user(request): @@ -146,7 +109,9 @@ def new_user(request): Django User form. """ - user = AdherentCreationForm(request.POST or None, request.FILES or None, user=request.user) + user = AdherentCreationForm( + request.POST or None, request.FILES or None, user=request.user + ) user.request = request GTU_sum_up = GeneralOption.get_cached_value("GTU_sum_up") @@ -238,7 +203,9 @@ def edit_club_admin_members(request, club_instance, **_kwargs): Django User form. """ - club = ClubAdminandMembersForm(request.POST or None, request.FILES or None, instance=club_instance) + club = ClubAdminandMembersForm( + request.POST or None, request.FILES or None, instance=club_instance + ) if club.is_valid(): if club.changed_data: club.save() @@ -247,7 +214,11 @@ def edit_club_admin_members(request, club_instance, **_kwargs): reverse("users:profil", kwargs={"userid": str(club_instance.id)}) ) return form( - {"userform": club, "showCGU": False, "action_name": _("Edit"),}, + { + "userform": club, + "showCGU": False, + "action_name": _("Edit"), + }, "users/user.html", request, ) @@ -269,11 +240,17 @@ def edit_info(request, user, userid): """ if user.is_class_adherent: user_form = AdherentEditForm( - request.POST or None, request.FILES or None, instance=user.adherent, user=request.user + request.POST or None, + request.FILES or None, + instance=user.adherent, + user=request.user, ) else: user_form = ClubForm( - request.POST or None, request.FILES or None,instance=user.club, user=request.user + request.POST or None, + request.FILES or None, + instance=user.club, + user=request.user, ) if user_form.is_valid(): if user_form.changed_data: @@ -286,7 +263,9 @@ def edit_info(request, user, userid): return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( - {"userform": user_form, "action_name": _("Edit")}, "users/user.html", request, + {"userform": user_form, "action_name": _("Edit")}, + "users/user.html", + request, ) @@ -316,7 +295,9 @@ def state(request, user, userid): ) return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( - {"userform": state_form, "action_name": _("Edit")}, "users/user.html", request, + {"userform": state_form, "action_name": _("Edit")}, + "users/user.html", + request, ) @@ -342,7 +323,9 @@ def groups(request, user, userid): messages.success(request, _("The groups were edited.")) return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( - {"userform": group_form, "action_name": _("Edit")}, "users/user.html", request, + {"userform": group_form, "action_name": _("Edit")}, + "users/user.html", + request, ) @@ -441,7 +424,9 @@ def new_serviceuser(request): messages.success(request, _("The service user was created.")) return redirect(reverse("users:index-serviceusers")) return form( - {"userform": user, "action_name": _("Add")}, "users/user.html", request, + {"userform": user, "action_name": _("Add")}, + "users/user.html", + request, ) @@ -468,7 +453,9 @@ def edit_serviceuser(request, serviceuser, **_kwargs): messages.success(request, _("The service user was edited.")) return redirect(reverse("users:index-serviceusers")) return form( - {"userform": serviceuser, "action_name": _("Edit")}, "users/user.html", request, + {"userform": serviceuser, "action_name": _("Edit")}, + "users/user.html", + request, ) @@ -606,7 +593,9 @@ def add_whitelist(request, user, userid): request, _("Warning: this user already has an active whitelist.") ) return form( - {"userform": whitelist, "action_name": _("Add")}, "users/user.html", request, + {"userform": whitelist, "action_name": _("Add")}, + "users/user.html", + request, ) @@ -633,7 +622,9 @@ def edit_whitelist(request, whitelist_instance, **_kwargs): messages.success(request, _("The whitelist was edited.")) return redirect(reverse("users:index")) return form( - {"userform": whitelist, "action_name": _("Edit")}, "users/user.html", request, + {"userform": whitelist, "action_name": _("Edit")}, + "users/user.html", + request, ) @@ -688,7 +679,11 @@ def add_emailaddress(request, user, userid): messages.success(request, _("The local email account was created.")) return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( - {"userform": emailaddress, "showCGU": False, "action_name": _("Add"),}, + { + "userform": emailaddress, + "showCGU": False, + "action_name": _("Add"), + }, "users/user.html", request, ) @@ -722,7 +717,11 @@ def edit_emailaddress(request, emailaddress_instance, **_kwargs): ) ) return form( - {"userform": emailaddress, "showCGU": False, "action_name": _("Edit"),}, + { + "userform": emailaddress, + "showCGU": False, + "action_name": _("Edit"), + }, "users/user.html", request, ) @@ -819,7 +818,9 @@ def add_school(request): messages.success(request, _("The school was added.")) return redirect(reverse("users:index-school")) return form( - {"userform": school, "action_name": _("Add")}, "users/user.html", request, + {"userform": school, "action_name": _("Add")}, + "users/user.html", + request, ) @@ -845,7 +846,9 @@ def edit_school(request, school_instance, **_kwargs): messages.success(request, _("The school was edited.")) return redirect(reverse("users:index-school")) return form( - {"userform": school, "action_name": _("Edit")}, "users/user.html", request, + {"userform": school, "action_name": _("Edit")}, + "users/user.html", + request, ) @@ -934,7 +937,9 @@ def edit_shell(request, shell_instance, **_kwargs): messages.success(request, _("The shell was edited.")) return redirect(reverse("users:index-shell")) return form( - {"userform": shell, "action_name": _("Edit")}, "users/user.html", request, + {"userform": shell, "action_name": _("Edit")}, + "users/user.html", + request, ) @@ -1310,7 +1315,7 @@ def index_serviceusers(request): def mon_profil(request): """Shortcuts view to profil view, with correct arguments. Returns the view profil with users argument, users is set to - default request.user. + default request.user. Parameters: request (django request): Standard django request. @@ -1335,7 +1340,7 @@ def profil(request, users, **_kwargs): * Email Settings of User instance * Tickets belonging to User instance. Requires the acl can_view on user instance. - + Parameters: request (django request): Standard django request. users: User instance to display profil @@ -1353,7 +1358,7 @@ def profil(request, users, **_kwargs): ] nb_machines = users.user_interfaces().count() - + bans = Ban.objects.filter(user=users) bans = SortTable.sort( bans, @@ -1379,7 +1384,7 @@ def profil(request, users, **_kwargs): "users/profil.html", { "users": users, - "nb_machines":nb_machines, + "nb_machines": nb_machines, "apps_templates_list": apps_templates_list, "ban_list": bans, "white_list": whitelists, @@ -1398,7 +1403,7 @@ def reset_password(request): """Reset password form, linked to form forgotten password. If an user is found, send an email to him with a link to reset its password. - + Parameters: request (django request): Standard django request. @@ -1594,6 +1599,7 @@ def initial_register(request): request, ) + @login_required @can_edit(User) def edit_theme(request, user, userid): @@ -1608,7 +1614,7 @@ def edit_theme(request, user, userid): Django User form. """ - theme_form = ThemeForm(request.POST or None, initial={'theme':user.theme}) + theme_form = ThemeForm(request.POST or None, initial={"theme": user.theme}) if theme_form.is_valid(): user.theme = theme_form.cleaned_data["theme"] user.save() @@ -1616,5 +1622,7 @@ def edit_theme(request, user, userid): return redirect(reverse("users:profil", kwargs={"userid": str(userid)})) return form( - {"userform": theme_form, "action_name": _("Edit")}, "users/user.html", request, - ) \ No newline at end of file + {"userform": theme_form, "action_name": _("Edit")}, + "users/user.html", + request, + ) diff --git a/users/views_autocomplete.py b/users/views_autocomplete.py index 014dd146..6b6f8dbe 100644 --- a/users/views_autocomplete.py +++ b/users/views_autocomplete.py @@ -31,13 +31,13 @@ Here are defined the autocomplete class based view. """ from __future__ import unicode_literals -from .models import User, School, Adherent, Club, ListShell - -from re2o.views import AutocompleteViewMixin, AutocompleteLoggedOutViewMixin - -from django.db.models import Q, Value, CharField +from django.db.models import CharField, Q, Value from django.db.models.functions import Concat +from re2o.views import AutocompleteLoggedOutViewMixin, AutocompleteViewMixin + +from .models import Adherent, Club, ListShell, School, User + class SchoolAutocomplete(AutocompleteLoggedOutViewMixin): obj_type = School diff --git a/users/widgets.py b/users/widgets.py index 81cced82..66d5f315 100644 --- a/users/widgets.py +++ b/users/widgets.py @@ -1,23 +1,18 @@ -from django.forms.widgets import Input +from django.conf import settings from django.forms.utils import flatatt -from django.utils.safestring import mark_safe +from django.forms.widgets import Input from django.template import Template from django.template.loader import get_template -from django.conf import settings -from django.utils.translation import ugettext_lazy as _, get_language_bidi -from django.utils.dates import ( - WEEKDAYS, - WEEKDAYS_ABBR, - MONTHS, - MONTHS_3, - MONTHS_AP, - MONTHS_ALT, -) +from django.utils.dates import (MONTHS, MONTHS_3, MONTHS_ALT, MONTHS_AP, + WEEKDAYS, WEEKDAYS_ABBR) +from django.utils.safestring import mark_safe +from django.utils.translation import get_language_bidi +from django.utils.translation import ugettext_lazy as _ def list2str(str_iterable): """ - Utility function to return a string representing a list of string + Utility function to return a string representing a list of string :params str_iterable: An iterable object where each element is of type str :returns: A representation of the iterable as a list (e.g '["a", "b"]')