From 88d6f8dc8ad2194b963eb464bd8051ac60f1d370 Mon Sep 17 00:00:00 2001 From: klmp200 Date: Thu, 4 Oct 2018 21:16:02 +0200 Subject: [PATCH 1/7] CI: introduce black --- .gitlab-ci.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 30b00fce..5b5cb5ef 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,17 @@ -test: +stages: + - setup + - test + +setup: + stage: setup script: - apt-get update - apt-get install -y gettext - pip install -r requirements.txt + +test: + stage: test + script: - pip install coverage - ./manage.py compilemessages - coverage run ./manage.py test @@ -11,3 +20,9 @@ test: artifacts: paths: - coverage_report/ + +black: + stage: test + script: + - pip install black + - black --check . From 0581c667de6131e0810badad66852e2e573911a9 Mon Sep 17 00:00:00 2001 From: klmp200 Date: Fri, 5 Oct 2018 21:51:54 +0200 Subject: [PATCH 2/7] Club: blackify view file --- club/views.py | 488 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 314 insertions(+), 174 deletions(-) diff --git a/club/views.py b/club/views.py index 0266b7a5..1b460b2c 100644 --- a/club/views.py +++ b/club/views.py @@ -37,13 +37,25 @@ from ajax_select.fields import AutoCompleteSelectField from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404, redirect -from core.views import CanCreateMixin, CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, PageEditViewBase +from core.views import ( + CanCreateMixin, + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + TabedViewMixin, + PageEditViewBase, +) from core.views.forms import SelectDate, SelectDateTime from club.models import Club, Membership, Mailing, MailingSubscription from sith.settings import SITH_MAXIMUM_FREE_ROLE from counter.models import Selling, Counter from core.models import User, PageRev -from com.views import PosterListBaseView, PosterCreateBaseView, PosterEditBaseView, PosterDeleteBaseView +from com.views import ( + PosterListBaseView, + PosterCreateBaseView, + PosterEditBaseView, + PosterDeleteBaseView, +) from com.models import Poster from django.conf import settings @@ -54,7 +66,7 @@ from django.conf import settings class ClubEditForm(forms.ModelForm): class Meta: model = Club - fields = ['address', 'logo', 'short_description'] + fields = ["address", "logo", "short_description"] def __init__(self, *args, **kwargs): super(ClubEditForm, self).__init__(*args, **kwargs) @@ -64,36 +76,40 @@ class ClubEditForm(forms.ModelForm): class MailingForm(forms.ModelForm): class Meta: model = Mailing - fields = ('email', 'club', 'moderator') + fields = ("email", "club", "moderator") def __init__(self, *args, **kwargs): - club_id = kwargs.pop('club_id', None) - user_id = kwargs.pop('user_id', -1) # Remember 0 is treated as None + club_id = kwargs.pop("club_id", None) + user_id = kwargs.pop("user_id", -1) # Remember 0 is treated as None super(MailingForm, self).__init__(*args, **kwargs) if club_id: - self.fields['club'].queryset = Club.objects.filter(id=club_id) - self.fields['club'].initial = club_id - self.fields['club'].widget = forms.HiddenInput() + self.fields["club"].queryset = Club.objects.filter(id=club_id) + self.fields["club"].initial = club_id + self.fields["club"].widget = forms.HiddenInput() if user_id >= 0: - self.fields['moderator'].queryset = User.objects.filter(id=user_id) - self.fields['moderator'].initial = user_id - self.fields['moderator'].widget = forms.HiddenInput() + self.fields["moderator"].queryset = User.objects.filter(id=user_id) + self.fields["moderator"].initial = user_id + self.fields["moderator"].widget = forms.HiddenInput() class MailingSubscriptionForm(forms.ModelForm): class Meta: model = MailingSubscription - fields = ('mailing', 'user', 'email') + fields = ("mailing", "user", "email") def __init__(self, *args, **kwargs): - kwargs.pop('user_id', None) # For standart interface - club_id = kwargs.pop('club_id', None) + kwargs.pop("user_id", None) # For standart interface + club_id = kwargs.pop("club_id", None) super(MailingSubscriptionForm, self).__init__(*args, **kwargs) - self.fields['email'].required = False + self.fields["email"].required = False if club_id: - self.fields['mailing'].queryset = Mailing.objects.filter(club__id=club_id, is_moderated=True) + self.fields["mailing"].queryset = Mailing.objects.filter( + club__id=club_id, is_moderated=True + ) - user = AutoCompleteSelectField('users', label=_('User'), help_text=None, required=False) + user = AutoCompleteSelectField( + "users", label=_("User"), help_text=None, required=False + ) class ClubTabsMixin(TabedViewMixin): @@ -105,66 +121,105 @@ class ClubTabsMixin(TabedViewMixin): def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse('club:club_view', kwargs={'club_id': self.object.id}), - 'slug': 'infos', - 'name': _("Infos"), - }) + tab_list.append( + { + "url": reverse("club:club_view", kwargs={"club_id": self.object.id}), + "slug": "infos", + "name": _("Infos"), + } + ) if self.request.user.can_view(self.object): - tab_list.append({ - 'url': reverse('club:club_members', kwargs={'club_id': self.object.id}), - 'slug': 'members', - 'name': _("Members"), - }) - tab_list.append({ - 'url': reverse('club:club_old_members', kwargs={'club_id': self.object.id}), - 'slug': 'elderlies', - 'name': _("Old members"), - }) + tab_list.append( + { + "url": reverse( + "club:club_members", kwargs={"club_id": self.object.id} + ), + "slug": "members", + "name": _("Members"), + } + ) + tab_list.append( + { + "url": reverse( + "club:club_old_members", kwargs={"club_id": self.object.id} + ), + "slug": "elderlies", + "name": _("Old members"), + } + ) if self.object.page: - tab_list.append({ - 'url': reverse('club:club_hist', kwargs={'club_id': self.object.id}), - 'slug': 'history', - 'name': _("History"), - }) + tab_list.append( + { + "url": reverse( + "club:club_hist", kwargs={"club_id": self.object.id} + ), + "slug": "history", + "name": _("History"), + } + ) if self.request.user.can_edit(self.object): - tab_list.append({ - 'url': reverse('club:tools', kwargs={'club_id': self.object.id}), - 'slug': 'tools', - 'name': _("Tools"), - }) - tab_list.append({ - 'url': reverse('club:club_edit', kwargs={'club_id': self.object.id}), - 'slug': 'edit', - 'name': _("Edit"), - }) + tab_list.append( + { + "url": reverse("club:tools", kwargs={"club_id": self.object.id}), + "slug": "tools", + "name": _("Tools"), + } + ) + tab_list.append( + { + "url": reverse( + "club:club_edit", kwargs={"club_id": self.object.id} + ), + "slug": "edit", + "name": _("Edit"), + } + ) if self.object.page and self.request.user.can_edit(self.object.page): - tab_list.append({ - 'url': reverse('core:page_edit', kwargs={'page_name': self.object.page.get_full_name()}), - 'slug': 'page_edit', - 'name': _('Edit club page') - }) - tab_list.append({ - 'url': reverse('club:club_sellings', kwargs={'club_id': self.object.id}), - 'slug': 'sellings', - 'name': _("Sellings"), - }) - tab_list.append({ - 'url': reverse('club:mailing', kwargs={'club_id': self.object.id}), - 'slug': 'mailing', - 'name': _("Mailing list"), - }) - tab_list.append({ - 'url': reverse('club:poster_list', kwargs={'club_id': self.object.id}), - 'slug': 'posters', - 'name': _("Posters list"), - }) + tab_list.append( + { + "url": reverse( + "core:page_edit", + kwargs={"page_name": self.object.page.get_full_name()}, + ), + "slug": "page_edit", + "name": _("Edit club page"), + } + ) + tab_list.append( + { + "url": reverse( + "club:club_sellings", kwargs={"club_id": self.object.id} + ), + "slug": "sellings", + "name": _("Sellings"), + } + ) + tab_list.append( + { + "url": reverse("club:mailing", kwargs={"club_id": self.object.id}), + "slug": "mailing", + "name": _("Mailing list"), + } + ) + tab_list.append( + { + "url": reverse( + "club:poster_list", kwargs={"club_id": self.object.id} + ), + "slug": "posters", + "name": _("Posters list"), + } + ) if self.request.user.is_owner(self.object): - tab_list.append({ - 'url': reverse('club:club_prop', kwargs={'club_id': self.object.id}), - 'slug': 'props', - 'name': _("Props"), - }) + tab_list.append( + { + "url": reverse( + "club:club_prop", kwargs={"club_id": self.object.id} + ), + "slug": "props", + "name": _("Props"), + } + ) return tab_list @@ -172,23 +227,25 @@ class ClubListView(ListView): """ List the Clubs """ + model = Club - template_name = 'club/club_list.jinja' + template_name = "club/club_list.jinja" class ClubView(ClubTabsMixin, DetailView): """ Front page of a Club """ + model = Club pk_url_kwarg = "club_id" - template_name = 'club/club_detail.jinja' + template_name = "club/club_detail.jinja" current_tab = "infos" def get_context_data(self, **kwargs): kwargs = super(ClubView, self).get_context_data(**kwargs) if self.object.page and self.object.page.revisions.exists(): - kwargs['page_revision'] = self.object.page.revisions.last().content + kwargs["page_revision"] = self.object.page.revisions.last().content return kwargs @@ -196,23 +253,24 @@ class ClubRevView(ClubView): """ Display a specific page revision """ + def dispatch(self, request, *args, **kwargs): obj = self.get_object() - self.revision = get_object_or_404(PageRev, pk=kwargs['rev_id'], page__club=obj) + self.revision = get_object_or_404(PageRev, pk=kwargs["rev_id"], page__club=obj) return super(ClubRevView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(ClubRevView, self).get_context_data(**kwargs) - kwargs['page_revision'] = self.revision.content + kwargs["page_revision"] = self.revision.content return kwargs class ClubPageEditView(ClubTabsMixin, PageEditViewBase): - template_name = 'club/pagerev_edit.jinja' + template_name = "club/pagerev_edit.jinja" current_tab = "page_edit" def dispatch(self, request, *args, **kwargs): - self.club = get_object_or_404(Club, pk=kwargs['club_id']) + self.club = get_object_or_404(Club, pk=kwargs["club_id"]) if not self.club.page: raise Http404 return super(ClubPageEditView, self).dispatch(request, *args, **kwargs) @@ -222,16 +280,17 @@ class ClubPageEditView(ClubTabsMixin, PageEditViewBase): return self._get_revision() def get_success_url(self, **kwargs): - return reverse_lazy('club:club_view', kwargs={'club_id': self.club.id}) + return reverse_lazy("club:club_view", kwargs={"club_id": self.club.id}) class ClubPageHistView(ClubTabsMixin, CanViewMixin, DetailView): """ Modification hostory of the page """ + model = Club pk_url_kwarg = "club_id" - template_name = 'club/page_history.jinja' + template_name = "club/page_history.jinja" current_tab = "history" @@ -239,9 +298,10 @@ class ClubToolsView(ClubTabsMixin, CanEditMixin, DetailView): """ Tools page of a Club """ + model = Club pk_url_kwarg = "club_id" - template_name = 'club/club_tools.jinja' + template_name = "club/club_tools.jinja" current_tab = "tools" @@ -249,16 +309,18 @@ class ClubMemberForm(forms.ModelForm): """ Form handling the members of a club """ - error_css_class = 'error' - required_css_class = 'required' + + error_css_class = "error" + required_css_class = "required" class Meta: model = Membership - fields = ['user', 'role', 'start_date', 'description'] - widgets = { - 'start_date': SelectDate - } - user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None) + fields = ["user", "role", "start_date", "description"] + widgets = {"start_date": SelectDate} + + user = AutoCompleteSelectField( + "users", required=True, label=_("Select user"), help_text=None + ) def save(self, *args, **kwargs): """ @@ -272,10 +334,11 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView): """ View of a club's members """ + model = Club pk_url_kwarg = "club_id" form_class = ClubMemberForm - template_name = 'club/club_members.jinja' + template_name = "club/club_members.jinja" current_tab = "members" def get_form(self): @@ -284,12 +347,19 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView): That's why the save method of ClubMemberForm is overridden. """ form = super(ClubMembersView, self).get_form() - if 'user' in form.data and form.data.get('user') != '': # Load an existing membership if possible - form.instance = Membership.objects.filter(club=self.object).filter(user=form.data.get('user')).filter(end_date=None).first() + if ( + "user" in form.data and form.data.get("user") != "" + ): # Load an existing membership if possible + form.instance = ( + Membership.objects.filter(club=self.object) + .filter(user=form.data.get("user")) + .filter(end_date=None) + .first() + ) if form.instance is None: # Instanciate a new membership form.instance = Membership(club=self.object, user=self.request.user) if not self.request.user.is_root: - form.fields.pop('start_date', None) + form.fields.pop("start_date", None) return form def form_valid(self, form): @@ -298,9 +368,12 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView): """ user = self.request.user ms = self.object.get_membership_for(user) - if (form.cleaned_data['role'] <= SITH_MAXIMUM_FREE_ROLE or - (ms is not None and ms.role >= form.cleaned_data['role']) or - user.is_board_member or user.is_root): + if ( + form.cleaned_data["role"] <= SITH_MAXIMUM_FREE_ROLE + or (ms is not None and ms.role >= form.cleaned_data["role"]) + or user.is_board_member + or user.is_root + ): form.save() form = self.form_class() return super(ModelFormMixin, self).form_valid(form) @@ -313,39 +386,57 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView): return super(ClubMembersView, self).dispatch(request, *args, **kwargs) def get_success_url(self, **kwargs): - return reverse_lazy('club:club_members', kwargs={'club_id': self.club.id}) + return reverse_lazy("club:club_members", kwargs={"club_id": self.club.id}) class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView): """ Old members of a club """ + model = Club pk_url_kwarg = "club_id" - template_name = 'club/club_old_members.jinja' + template_name = "club/club_old_members.jinja" current_tab = "elderlies" class SellingsFormBase(forms.Form): - begin_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Begin date"), required=False, widget=SelectDateTime) - end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), required=False, widget=SelectDateTime) - counter = forms.ModelChoiceField(Counter.objects.order_by('name').all(), label=_("Counter"), required=False) + begin_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Begin date"), + required=False, + widget=SelectDateTime, + ) + end_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End date"), + required=False, + widget=SelectDateTime, + ) + counter = forms.ModelChoiceField( + Counter.objects.order_by("name").all(), label=_("Counter"), required=False + ) class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView): """ Sellings of a club """ + model = Club pk_url_kwarg = "club_id" - template_name = 'club/club_sellings.jinja' + template_name = "club/club_sellings.jinja" current_tab = "sellings" def get_form_class(self): kwargs = { - 'product': forms.ModelChoiceField(self.object.products.order_by('name').all(), label=_("Product"), required=False) + "product": forms.ModelChoiceField( + self.object.products.order_by("name").all(), + label=_("Product"), + required=False, + ) } - return type('SellingsForm', (SellingsFormBase,), kwargs) + return type("SellingsForm", (SellingsFormBase,), kwargs) def get_context_data(self, **kwargs): kwargs = super(ClubSellingView, self).get_context_data(**kwargs) @@ -354,21 +445,23 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView): if form.is_valid(): if not len([v for v in form.cleaned_data.values() if v is not None]): qs = Selling.objects.filter(id=-1) - if form.cleaned_data['begin_date']: - qs = qs.filter(date__gte=form.cleaned_data['begin_date']) - if form.cleaned_data['end_date']: - qs = qs.filter(date__lte=form.cleaned_data['end_date']) - if form.cleaned_data['counter']: - qs = qs.filter(counter=form.cleaned_data['counter']) - if form.cleaned_data['product']: - qs = qs.filter(product__id=form.cleaned_data['product'].id) - kwargs['result'] = qs.all().order_by('-id') - kwargs['total'] = sum([s.quantity * s.unit_price for s in qs.all()]) - kwargs['total_quantity'] = sum([s.quantity for s in qs.all()]) - kwargs['benefit'] = kwargs['total'] - sum([s.product.purchase_price for s in qs.exclude(product=None)]) + if form.cleaned_data["begin_date"]: + qs = qs.filter(date__gte=form.cleaned_data["begin_date"]) + if form.cleaned_data["end_date"]: + qs = qs.filter(date__lte=form.cleaned_data["end_date"]) + if form.cleaned_data["counter"]: + qs = qs.filter(counter=form.cleaned_data["counter"]) + if form.cleaned_data["product"]: + qs = qs.filter(product__id=form.cleaned_data["product"].id) + kwargs["result"] = qs.all().order_by("-id") + kwargs["total"] = sum([s.quantity * s.unit_price for s in qs.all()]) + kwargs["total_quantity"] = sum([s.quantity for s in qs.all()]) + kwargs["benefit"] = kwargs["total"] - sum( + [s.product.purchase_price for s in qs.exclude(product=None)] + ) else: - kwargs['result'] = qs[:0] - kwargs['form'] = form + kwargs["result"] = qs[:0] + kwargs["form"] = form return kwargs @@ -379,36 +472,56 @@ class ClubSellingCSVView(ClubSellingView): def get(self, request, *args, **kwargs): import csv - response = HttpResponse(content_type='text/csv') + + response = HttpResponse(content_type="text/csv") self.object = self.get_object() name = _("Sellings") + "_" + self.object.name + ".csv" - response['Content-Disposition'] = 'filename=' + name + response["Content-Disposition"] = "filename=" + name kwargs = self.get_context_data(**kwargs) - writer = csv.writer(response, delimiter=";", lineterminator='\n', quoting=csv.QUOTE_ALL) + writer = csv.writer( + response, delimiter=";", lineterminator="\n", quoting=csv.QUOTE_ALL + ) - writer.writerow([_t('Quantity'), kwargs['total_quantity']]) - writer.writerow([_t('Total'), kwargs['total']]) - writer.writerow([_t('Benefit'), kwargs['benefit']]) - writer.writerow([_t('Date'), _t('Counter'), _t('Barman'), _t('Customer'), _t('Label'), - _t('Quantity'), _t('Total'), _t('Payment method'), _t('Selling price'), _t('Purchase price'), _t('Benefit')]) - for o in kwargs['result']: + writer.writerow([_t("Quantity"), kwargs["total_quantity"]]) + writer.writerow([_t("Total"), kwargs["total"]]) + writer.writerow([_t("Benefit"), kwargs["benefit"]]) + writer.writerow( + [ + _t("Date"), + _t("Counter"), + _t("Barman"), + _t("Customer"), + _t("Label"), + _t("Quantity"), + _t("Total"), + _t("Payment method"), + _t("Selling price"), + _t("Purchase price"), + _t("Benefit"), + ] + ) + for o in kwargs["result"]: row = [o.date, o.counter] if o.seller: row.append(o.seller.get_display_name()) else: - row.append('') + row.append("") if o.customer: row.append(o.customer.user.get_display_name()) else: - row.append('') - row = row + [o.label, o.quantity, o.quantity * o.unit_price, - o.get_payment_method_display()] + row.append("") + row = row + [ + o.label, + o.quantity, + o.quantity * o.unit_price, + o.get_payment_method_display(), + ] if o.product: row.append(o.product.selling_price) row.append(o.product.purchase_price) row.append(o.product.selling_price - o.product.purchase_price) else: - row = row + ['', '', ''] + row = row + ["", "", ""] writer.writerow(row) return response @@ -418,10 +531,11 @@ class ClubEditView(ClubTabsMixin, CanEditMixin, UpdateView): """ Edit a Club's main informations (for the club's members) """ + model = Club pk_url_kwarg = "club_id" form_class = ClubEditForm - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "edit" @@ -429,10 +543,11 @@ class ClubEditPropView(ClubTabsMixin, CanEditPropMixin, UpdateView): """ Edit the properties of a Club object (for the Sith admins) """ + model = Club pk_url_kwarg = "club_id" - fields = ['name', 'unix_name', 'parent', 'is_active'] - template_name = 'core/edit.jinja' + fields = ["name", "unix_name", "parent", "is_active"] + template_name = "core/edit.jinja" current_tab = "props" @@ -440,16 +555,18 @@ class ClubCreateView(CanEditPropMixin, CreateView): """ Create a club (for the Sith admin) """ + model = Club pk_url_kwarg = "club_id" - fields = ['name', 'unix_name', 'parent'] - template_name = 'core/edit.jinja' + fields = ["name", "unix_name", "parent"] + template_name = "core/edit.jinja" class MembershipSetOldView(CanEditMixin, DetailView): """ Set a membership as beeing old """ + model = Membership pk_url_kwarg = "membership_id" @@ -457,11 +574,23 @@ class MembershipSetOldView(CanEditMixin, DetailView): self.object = self.get_object() self.object.end_date = timezone.now() self.object.save() - return HttpResponseRedirect(reverse('club:club_members', args=self.args, kwargs={'club_id': self.object.club.id})) + return HttpResponseRedirect( + reverse( + "club:club_members", + args=self.args, + kwargs={"club_id": self.object.club.id}, + ) + ) def post(self, request, *args, **kwargs): self.object = self.get_object() - return HttpResponseRedirect(reverse('club:club_members', args=self.args, kwargs={'club_id': self.object.club.id})) + return HttpResponseRedirect( + reverse( + "club:club_members", + args=self.args, + kwargs={"club_id": self.object.club.id}, + ) + ) class ClubStatView(TemplateView): @@ -469,7 +598,7 @@ class ClubStatView(TemplateView): def get_context_data(self, **kwargs): kwargs = super(ClubStatView, self).get_context_data(**kwargs) - kwargs['club_list'] = Club.objects.all() + kwargs["club_list"] = Club.objects.all() return kwargs @@ -477,16 +606,21 @@ class ClubMailingView(ClubTabsMixin, ListView): """ A list of mailing for a given club """ + action = None model = Mailing template_name = "club/mailing.jinja" - current_tab = 'mailing' + current_tab = "mailing" def authorized(self): - return self.club.has_rights_in_club(self.user) or self.user.is_root or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + return ( + self.club.has_rights_in_club(self.user) + or self.user.is_root + or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + ) def dispatch(self, request, *args, **kwargs): - self.club = get_object_or_404(Club, pk=kwargs['club_id']) + self.club = get_object_or_404(Club, pk=kwargs["club_id"]) self.user = request.user self.object = self.club if not self.authorized(): @@ -504,7 +638,9 @@ class ClubMailingView(ClubTabsMixin, ListView): elif self.action == "add_member": form = MailingSubscriptionForm model = MailingSubscription - return MailingGenericCreateView.as_view(model=model, list_view=self, form_class=form)(request, *args, **kwargs) + return MailingGenericCreateView.as_view( + model=model, list_view=self, form_class=form + )(request, *args, **kwargs) return res def get_queryset(self): @@ -512,11 +648,11 @@ class ClubMailingView(ClubTabsMixin, ListView): def get_context_data(self, **kwargs): kwargs = super(ClubMailingView, self).get_context_data(**kwargs) - kwargs['add_member'] = self.member_form - kwargs['add_mailing'] = self.mailing_form - kwargs['club'] = self.club - kwargs['user'] = self.user - kwargs['has_objects'] = len(kwargs['object_list']) > 0 + kwargs["add_member"] = self.member_form + kwargs["add_mailing"] = self.mailing_form + kwargs["club"] = self.club + kwargs["user"] = self.user + kwargs["has_objects"] = len(kwargs["object_list"]) > 0 return kwargs def get_object(self): @@ -527,20 +663,23 @@ class MailingGenericCreateView(CreateView, SingleObjectMixin): """ Create a new mailing list """ + list_view = None form_class = None def get_context_data(self, **kwargs): view_kwargs = self.list_view.get_context_data(**kwargs) - for key, data in super(MailingGenericCreateView, self).get_context_data(**kwargs).items(): + for key, data in ( + super(MailingGenericCreateView, self).get_context_data(**kwargs).items() + ): view_kwargs[key] = data - view_kwargs[self.list_view.action] = view_kwargs['form'] + view_kwargs[self.list_view.action] = view_kwargs["form"] return view_kwargs def get_form_kwargs(self): kwargs = super(MailingGenericCreateView, self).get_form_kwargs() - kwargs['club_id'] = self.list_view.club.id - kwargs['user_id'] = self.list_view.user.id + kwargs["club_id"] = self.list_view.club.id + kwargs["user_id"] = self.list_view.user.id return kwargs def dispatch(self, request, *args, **kwargs): @@ -550,13 +689,13 @@ class MailingGenericCreateView(CreateView, SingleObjectMixin): return super(MailingGenericCreateView, self).dispatch(request, *args, **kwargs) def get_success_url(self, **kwargs): - return reverse_lazy('club:mailing', kwargs={'club_id': self.list_view.club.id}) + return reverse_lazy("club:mailing", kwargs={"club_id": self.list_view.club.id}) class MailingDeleteView(CanEditMixin, DeleteView): model = Mailing - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" pk_url_kwarg = "mailing_id" redirect_page = None @@ -568,27 +707,28 @@ class MailingDeleteView(CanEditMixin, DeleteView): if self.redirect_page: return reverse_lazy(self.redirect_page) else: - return reverse_lazy('club:mailing', kwargs={'club_id': self.club_id}) + return reverse_lazy("club:mailing", kwargs={"club_id": self.club_id}) class MailingSubscriptionDeleteView(CanEditMixin, DeleteView): model = MailingSubscription - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" pk_url_kwarg = "mailing_subscription_id" def dispatch(self, request, *args, **kwargs): self.club_id = self.get_object().mailing.club.id - return super(MailingSubscriptionDeleteView, self).dispatch(request, *args, **kwargs) + return super(MailingSubscriptionDeleteView, self).dispatch( + request, *args, **kwargs + ) def get_success_url(self, **kwargs): - return reverse_lazy('club:mailing', kwargs={'club_id': self.club_id}) + return reverse_lazy("club:mailing", kwargs={"club_id": self.club_id}) class MailingAutoGenerationView(View): - def dispatch(self, request, *args, **kwargs): - self.mailing = get_object_or_404(Mailing, pk=kwargs['mailing_id']) + self.mailing = get_object_or_404(Mailing, pk=kwargs["mailing_id"]) if not request.user.can_edit(self.mailing): raise PermissionDenied return super(MailingAutoGenerationView, self).dispatch(request, *args, **kwargs) @@ -596,23 +736,24 @@ class MailingAutoGenerationView(View): def get(self, request, *args, **kwargs): club = self.mailing.club self.mailing.subscriptions.all().delete() - members = club.members.filter(role__gte=settings.SITH_CLUB_ROLES_ID['Board member']).exclude(end_date__lte=timezone.now()) + members = club.members.filter( + role__gte=settings.SITH_CLUB_ROLES_ID["Board member"] + ).exclude(end_date__lte=timezone.now()) for member in members.all(): MailingSubscription(user=member.user, mailing=self.mailing).save() - return redirect('club:mailing', club_id=club.id) + return redirect("club:mailing", club_id=club.id) class MailingAutoCleanView(View): - def dispatch(self, request, *args, **kwargs): - self.mailing = get_object_or_404(Mailing, pk=kwargs['mailing_id']) + self.mailing = get_object_or_404(Mailing, pk=kwargs["mailing_id"]) if not request.user.can_edit(self.mailing): raise PermissionDenied return super(MailingAutoCleanView, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): self.mailing.subscriptions.all().delete() - return redirect('club:mailing', club_id=self.mailing.club.id) + return redirect("club:mailing", club_id=self.mailing.club.id) class PosterListView(ClubTabsMixin, PosterListBaseView, CanViewMixin): @@ -623,8 +764,8 @@ class PosterListView(ClubTabsMixin, PosterListBaseView, CanViewMixin): def get_context_data(self, **kwargs): kwargs = super(PosterListView, self).get_context_data(**kwargs) - kwargs['app'] = "club" - kwargs['club'] = self.club + kwargs["app"] = "club" + kwargs["club"] = self.club return kwargs @@ -640,18 +781,18 @@ class PosterCreateView(PosterCreateBaseView, CanCreateMixin): return obj def get_success_url(self, **kwargs): - return reverse_lazy('club:poster_list', kwargs={'club_id': self.club.id}) + return reverse_lazy("club:poster_list", kwargs={"club_id": self.club.id}) class PosterEditView(ClubTabsMixin, PosterEditBaseView, CanEditMixin): """Edit communication poster""" def get_success_url(self): - return reverse_lazy('club:poster_list', kwargs={'club_id': self.club.id}) + return reverse_lazy("club:poster_list", kwargs={"club_id": self.club.id}) def get_context_data(self, **kwargs): kwargs = super(PosterEditView, self).get_context_data(**kwargs) - kwargs['app'] = "club" + kwargs["app"] = "club" return kwargs @@ -659,5 +800,4 @@ class PosterDeleteView(PosterDeleteBaseView, ClubTabsMixin, CanEditMixin): """Delete communication poster""" def get_success_url(self): - return reverse_lazy('club:poster_list', kwargs={'club_id': self.club.id}) - + return reverse_lazy("club:poster_list", kwargs={"club_id": self.club.id}) From cb58b00b6e761f2a7242ffe2a2a6d8a5edde2520 Mon Sep 17 00:00:00 2001 From: klmp200 Date: Thu, 4 Oct 2018 21:29:19 +0200 Subject: [PATCH 3/7] All: Apply Black coding rules --- .gitlab-ci.yml | 4 +- README.md | 1 + accounting/__init__.py | 1 - accounting/migrations/0001_initial.py | 302 +++- .../migrations/0002_auto_20160824_2152.py | 92 +- .../migrations/0003_auto_20160824_2203.py | 48 +- .../migrations/0004_auto_20161005_1505.py | 43 +- .../migrations/0005_auto_20170324_0917.py | 14 +- accounting/models.py | 273 ++-- accounting/tests.py | 320 ++-- accounting/urls.py | 147 +- accounting/views.py | 553 ++++--- api/__init__.py | 1 - api/urls.py | 35 +- api/views/__init__.py | 20 +- api/views/api.py | 7 +- api/views/club.py | 13 +- api/views/counter.py | 6 +- api/views/group.py | 1 - api/views/launderette.py | 50 +- api/views/user.py | 13 +- club/__init__.py | 1 - club/migrations/0001_initial.py | 92 +- club/migrations/0002_auto_20160824_2152.py | 58 +- club/migrations/0003_auto_20160902_2042.py | 12 +- club/migrations/0004_auto_20160915_1057.py | 16 +- club/migrations/0005_auto_20161120_1149.py | 19 +- club/migrations/0006_auto_20161229_0040.py | 14 +- club/migrations/0007_auto_20170324_0917.py | 9 +- club/migrations/0008_auto_20170515_2214.py | 10 +- club/migrations/0009_auto_20170822_2232.py | 95 +- club/migrations/0010_auto_20170912_2028.py | 40 +- club/migrations/0010_club_logo.py | 14 +- club/migrations/0011_auto_20180426_2013.py | 16 +- club/models.py | 237 ++- club/tests.py | 124 +- club/urls.py | 118 +- com/__init__.py | 1 - com/admin.py | 1 - com/migrations/0001_initial.py | 36 +- com/migrations/0002_news_newsdate.py | 101 +- com/migrations/0003_auto_20170115_2300.py | 87 +- com/migrations/0004_auto_20171221_1614.py | 78 +- com/migrations/0005_auto_20180318_2227.py | 12 +- com/models.py | 138 +- com/tests.py | 34 +- com/urls.py | 121 +- com/views.py | 559 ++++--- core/__init__.py | 2 +- core/admin.py | 40 +- core/apps.py | 12 +- core/lookups.py | 30 +- core/management/commands/__init__.py | 1 - core/management/commands/check_fs.py | 10 +- core/management/commands/compilestatic.py | 16 +- core/management/commands/markdown.py | 9 +- core/management/commands/populate.py | 915 ++++++++--- core/management/commands/repair_fs.py | 10 +- core/management/commands/setup.py | 18 +- core/markdown.py | 116 +- core/middleware.py | 10 +- core/migrations/0001_initial.py | 632 ++++++-- core/migrations/0002_auto_20160831_0144.py | 12 +- core/migrations/0003_auto_20160902_1914.py | 24 +- core/migrations/0004_user_godfathers.py | 14 +- core/migrations/0005_auto_20161105_1035.py | 25 +- core/migrations/0006_auto_20161108_1703.py | 12 +- .../0008_sithfile_asked_for_removal.py | 12 +- core/migrations/0009_auto_20161120_1155.py | 39 +- core/migrations/0010_sithfile_is_in_sas.py | 12 +- core/migrations/0011_auto_20161124_0848.py | 48 +- core/migrations/0012_notification.py | 51 +- core/migrations/0013_auto_20161209_2338.py | 23 +- core/migrations/0014_auto_20161210_0009.py | 24 +- core/migrations/0015_sithfile_moderator.py | 17 +- core/migrations/0016_auto_20161212_1922.py | 18 +- core/migrations/0017_auto_20161220_1626.py | 12 +- core/migrations/0018_auto_20161224_0211.py | 25 +- .../0019_preferences_receive_weekmail.py | 14 +- core/migrations/0020_auto_20170324_0917.py | 24 +- core/migrations/0021_auto_20170822_1529.py | 26 +- core/migrations/0022_auto_20170822_2232.py | 26 +- core/migrations/0023_auto_20170902_1226.py | 36 +- core/migrations/0024_auto_20170906_1317.py | 26 +- core/migrations/0025_auto_20170919_1521.py | 21 +- core/migrations/0026_auto_20170926_1512.py | 29 +- core/migrations/0027_gift.py | 34 +- core/migrations/0028_auto_20171216_2044.py | 30 +- core/migrations/0029_auto_20180426_2013.py | 17 +- core/models.py | 640 +++++--- core/operations.py | 2 +- core/scss/finder.py | 7 +- core/scss/processor.py | 5 +- core/templatetags/__init__.py | 1 - core/templatetags/renderer.py | 34 +- core/tests.py | 398 +++-- core/urls.py | 232 ++- core/utils.py | 151 +- core/views/__init__.py | 87 +- core/views/files.py | 233 ++- core/views/forms.py | 291 ++-- core/views/group.py | 7 +- core/views/page.py | 78 +- core/views/site.py | 44 +- core/views/user.py | 549 ++++--- counter/__init__.py | 1 - counter/migrations/0001_initial.py | 456 ++++-- counter/migrations/0002_auto_20160826_1342.py | 101 +- .../migrations/0003_permanency_activity.py | 16 +- counter/migrations/0004_auto_20160826_1907.py | 12 +- counter/migrations/0005_auto_20160826_2330.py | 31 +- counter/migrations/0006_auto_20160831_1304.py | 17 +- counter/migrations/0007_product_archived.py | 12 +- counter/migrations/0008_counter_token.py | 14 +- counter/migrations/0009_eticket.py | 37 +- counter/migrations/0010_auto_20161003_1900.py | 18 +- counter/migrations/0011_auto_20161004_2039.py | 14 +- counter/migrations/0012_auto_20170515_2202.py | 14 +- .../0013_customer_recorded_products.py | 27 +- counter/migrations/0014_auto_20170816_1521.py | 12 +- counter/migrations/0014_auto_20170817_1537.py | 12 +- counter/migrations/0015_merge.py | 7 +- .../migrations/0016_producttype_comment.py | 12 +- counter/models.py | 439 ++++-- counter/tests.py | 120 +- counter/urls.py | 135 +- counter/views.py | 1335 +++++++++++------ eboutic/__init__.py | 1 - eboutic/migrations/0001_initial.py | 139 +- eboutic/models.py | 53 +- eboutic/tests.py | 245 ++- eboutic/tests/test.py | 4 - eboutic/urls.py | 12 +- eboutic/views.py | 200 ++- election/migrations/0001_initial.py | 214 ++- election/migrations/0002_election_archived.py | 12 +- .../migrations/0003_auto_20171202_1819.py | 13 +- election/models.py | 100 +- election/tests.py | 50 +- election/urls.py | 64 +- election/views.py | 333 ++-- forum/__init__.py | 1 - forum/admin.py | 1 - forum/migrations/0001_initial.py | 224 ++- forum/migrations/0002_auto_20170312_1753.py | 28 +- forum/migrations/0003_auto_20170510_1754.py | 14 +- forum/migrations/0004_auto_20170531_1949.py | 70 +- .../0005_forumtopic_subscribed_users.py | 14 +- forum/migrations/0006_auto_20180426_2013.py | 26 +- forum/models.py | 183 ++- forum/urls.py | 73 +- forum/views.py | 149 +- launderette/__init__.py | 1 - launderette/migrations/0001_initial.py | 188 ++- launderette/models.py | 107 +- launderette/urls.py | 61 +- launderette/views.py | 304 ++-- matmat/urls.py | 8 +- matmat/views.py | 89 +- migrate.py | 1265 +++++++++------- rootplace/__init__.py | 1 - rootplace/urls.py | 4 +- rootplace/views.py | 16 +- sas/__init__.py | 1 - sas/migrations/0001_initial.py | 20 +- sas/migrations/0002_auto_20161119_1241.py | 33 +- sas/models.py | 129 +- sas/urls.py | 40 +- sas/views.py | 192 ++- sith/__init__.py | 1 - sith/settings.py | 497 +++--- sith/toolbar_debug.py | 4 +- sith/urls.py | 68 +- sith/wsgi.py | 2 +- stock/__init__.py | 1 - stock/admin.py | 2 +- stock/migrations/0001_initial.py | 178 ++- stock/models.py | 93 +- stock/urls.py | 83 +- stock/views.py | 361 +++-- subscription/__init__.py | 1 - subscription/admin.py | 7 +- subscription/migrations/0001_initial.py | 82 +- .../migrations/0002_auto_20160830_1719.py | 21 +- .../migrations/0003_auto_20160902_1914.py | 28 +- .../migrations/0004_auto_20170821_1849.py | 30 +- .../migrations/0005_auto_20170821_2054.py | 29 +- .../migrations/0006_auto_20170902_1222.py | 30 +- .../migrations/0007_auto_20180706_1135.py | 31 +- .../migrations/0008_auto_20180831_2016.py | 32 +- .../migrations/0009_auto_20180920_1421.py | 33 +- .../migrations/0010_auto_20180920_1441.py | 33 +- subscription/models.py | 178 ++- subscription/tests.py | 151 +- subscription/urls.py | 4 +- subscription/views.py | 122 +- trombi/__init__.py | 1 - trombi/migrations/0001_initial.py | 132 +- .../migrations/0002_trombi_show_profiles.py | 14 +- .../0003_trombicomment_is_moderated.py | 14 +- .../migrations/0004_trombiclubmembership.py | 51 +- trombi/models.py | 112 +- trombi/urls.py | 68 +- trombi/views.py | 313 ++-- 204 files changed, 13173 insertions(+), 6376 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5b5cb5ef..e94b7d00 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,11 +8,12 @@ setup: - apt-get update - apt-get install -y gettext - pip install -r requirements.txt + - pip install coverage + - pip install black test: stage: test script: - - pip install coverage - ./manage.py compilemessages - coverage run ./manage.py test - coverage html @@ -24,5 +25,4 @@ test: black: stage: test script: - - pip install black - black --check . diff --git a/README.md b/README.md index b82e3a49..e4e66955 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![pipeline status](https://ae-dev.utbm.fr/ae/Sith/badges/master/pipeline.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master) [![coverage report](https://ae-dev.utbm.fr/ae/Sith/badges/master/coverage.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) ## Sith AE diff --git a/accounting/__init__.py b/accounting/__init__.py index 0a9419f8..0ace29c4 100644 --- a/accounting/__init__.py +++ b/accounting/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/accounting/migrations/0001_initial.py b/accounting/migrations/0001_initial.py index 19b967bc..9bd829a6 100644 --- a/accounting/migrations/0001_initial.py +++ b/accounting/migrations/0001_initial.py @@ -8,103 +8,271 @@ import accounting.models class Migration(migrations.Migration): - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='AccountingType', + name="AccountingType", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('code', models.CharField(max_length=16, verbose_name='code', validators=[django.core.validators.RegexValidator('^[0-9]*$', 'An accounting type code contains only numbers')])), - ('label', models.CharField(max_length=128, verbose_name='label')), - ('movement_type', models.CharField(choices=[('CREDIT', 'Credit'), ('DEBIT', 'Debit'), ('NEUTRAL', 'Neutral')], max_length=12, verbose_name='movement type')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "code", + models.CharField( + max_length=16, + verbose_name="code", + validators=[ + django.core.validators.RegexValidator( + "^[0-9]*$", + "An accounting type code contains only numbers", + ) + ], + ), + ), + ("label", models.CharField(max_length=128, verbose_name="label")), + ( + "movement_type", + models.CharField( + choices=[ + ("CREDIT", "Credit"), + ("DEBIT", "Debit"), + ("NEUTRAL", "Neutral"), + ], + max_length=12, + verbose_name="movement type", + ), + ), ], options={ - 'verbose_name': 'accounting type', - 'ordering': ['movement_type', 'code'], + "verbose_name": "accounting type", + "ordering": ["movement_type", "code"], }, ), migrations.CreateModel( - name='BankAccount', + name="BankAccount", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('iban', models.CharField(max_length=255, blank=True, verbose_name='iban')), - ('number', models.CharField(max_length=255, blank=True, verbose_name='account number')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "iban", + models.CharField(max_length=255, blank=True, verbose_name="iban"), + ), + ( + "number", + models.CharField( + max_length=255, blank=True, verbose_name="account number" + ), + ), + ], + options={"verbose_name": "Bank account", "ordering": ["club", "name"]}, + ), + migrations.CreateModel( + name="ClubAccount", + fields=[ + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), ], options={ - 'verbose_name': 'Bank account', - 'ordering': ['club', 'name'], + "verbose_name": "Club account", + "ordering": ["bank_account", "name"], }, ), migrations.CreateModel( - name='ClubAccount', + name="Company", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=60, verbose_name="name")), ], - options={ - 'verbose_name': 'Club account', - 'ordering': ['bank_account', 'name'], - }, + options={"verbose_name": "company"}, ), migrations.CreateModel( - name='Company', + name="GeneralJournal", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=60, verbose_name='name')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("start_date", models.DateField(verbose_name="start date")), + ( + "end_date", + models.DateField( + null=True, verbose_name="end date", default=None, blank=True + ), + ), + ("name", models.CharField(max_length=40, verbose_name="name")), + ( + "closed", + models.BooleanField(verbose_name="is closed", default=False), + ), + ( + "amount", + accounting.models.CurrencyField( + decimal_places=2, + default=0, + verbose_name="amount", + max_digits=12, + ), + ), + ( + "effective_amount", + accounting.models.CurrencyField( + decimal_places=2, + default=0, + verbose_name="effective_amount", + max_digits=12, + ), + ), ], - options={ - 'verbose_name': 'company', - }, + options={"verbose_name": "General journal", "ordering": ["-start_date"]}, ), migrations.CreateModel( - name='GeneralJournal', + name="Operation", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('start_date', models.DateField(verbose_name='start date')), - ('end_date', models.DateField(null=True, verbose_name='end date', default=None, blank=True)), - ('name', models.CharField(max_length=40, verbose_name='name')), - ('closed', models.BooleanField(verbose_name='is closed', default=False)), - ('amount', accounting.models.CurrencyField(decimal_places=2, default=0, verbose_name='amount', max_digits=12)), - ('effective_amount', accounting.models.CurrencyField(decimal_places=2, default=0, verbose_name='effective_amount', max_digits=12)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("number", models.IntegerField(verbose_name="number")), + ( + "amount", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="amount" + ), + ), + ("date", models.DateField(verbose_name="date")), + ("remark", models.CharField(max_length=128, verbose_name="comment")), + ( + "mode", + models.CharField( + choices=[ + ("CHECK", "Check"), + ("CASH", "Cash"), + ("TRANSFERT", "Transfert"), + ("CARD", "Credit card"), + ], + max_length=255, + verbose_name="payment method", + ), + ), + ( + "cheque_number", + models.CharField( + max_length=32, + null=True, + verbose_name="cheque number", + default="", + blank=True, + ), + ), + ("done", models.BooleanField(verbose_name="is done", default=False)), + ( + "target_type", + models.CharField( + choices=[ + ("USER", "User"), + ("CLUB", "Club"), + ("ACCOUNT", "Account"), + ("COMPANY", "Company"), + ("OTHER", "Other"), + ], + max_length=10, + verbose_name="target type", + ), + ), + ( + "target_id", + models.IntegerField( + null=True, verbose_name="target id", blank=True + ), + ), + ( + "target_label", + models.CharField( + max_length=32, + blank=True, + verbose_name="target label", + default="", + ), + ), + ( + "accounting_type", + models.ForeignKey( + null=True, + related_name="operations", + verbose_name="accounting type", + to="accounting.AccountingType", + blank=True, + ), + ), ], - options={ - 'verbose_name': 'General journal', - 'ordering': ['-start_date'], - }, + options={"ordering": ["-number"]}, ), migrations.CreateModel( - name='Operation', + name="SimplifiedAccountingType", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('number', models.IntegerField(verbose_name='number')), - ('amount', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='amount')), - ('date', models.DateField(verbose_name='date')), - ('remark', models.CharField(max_length=128, verbose_name='comment')), - ('mode', models.CharField(choices=[('CHECK', 'Check'), ('CASH', 'Cash'), ('TRANSFERT', 'Transfert'), ('CARD', 'Credit card')], max_length=255, verbose_name='payment method')), - ('cheque_number', models.CharField(max_length=32, null=True, verbose_name='cheque number', default='', blank=True)), - ('done', models.BooleanField(verbose_name='is done', default=False)), - ('target_type', models.CharField(choices=[('USER', 'User'), ('CLUB', 'Club'), ('ACCOUNT', 'Account'), ('COMPANY', 'Company'), ('OTHER', 'Other')], max_length=10, verbose_name='target type')), - ('target_id', models.IntegerField(null=True, verbose_name='target id', blank=True)), - ('target_label', models.CharField(max_length=32, blank=True, verbose_name='target label', default='')), - ('accounting_type', models.ForeignKey(null=True, related_name='operations', verbose_name='accounting type', to='accounting.AccountingType', blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("label", models.CharField(max_length=128, verbose_name="label")), + ( + "accounting_type", + models.ForeignKey( + verbose_name="simplified accounting types", + to="accounting.AccountingType", + related_name="simplified_types", + ), + ), ], options={ - 'ordering': ['-number'], - }, - ), - migrations.CreateModel( - name='SimplifiedAccountingType', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('label', models.CharField(max_length=128, verbose_name='label')), - ('accounting_type', models.ForeignKey(verbose_name='simplified accounting types', to='accounting.AccountingType', related_name='simplified_types')), - ], - options={ - 'verbose_name': 'simplified type', - 'ordering': ['accounting_type__movement_type', 'accounting_type__code'], + "verbose_name": "simplified type", + "ordering": ["accounting_type__movement_type", "accounting_type__code"], }, ), ] diff --git a/accounting/migrations/0002_auto_20160824_2152.py b/accounting/migrations/0002_auto_20160824_2152.py index b44310ff..e0322f08 100644 --- a/accounting/migrations/0002_auto_20160824_2152.py +++ b/accounting/migrations/0002_auto_20160824_2152.py @@ -7,54 +7,88 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('club', '0001_initial'), - ('accounting', '0001_initial'), - ('core', '0001_initial'), + ("club", "0001_initial"), + ("accounting", "0001_initial"), + ("core", "0001_initial"), ] operations = [ migrations.AddField( - model_name='operation', - name='invoice', - field=models.ForeignKey(null=True, related_name='operations', verbose_name='invoice', to='core.SithFile', blank=True), + model_name="operation", + name="invoice", + field=models.ForeignKey( + null=True, + related_name="operations", + verbose_name="invoice", + to="core.SithFile", + blank=True, + ), ), migrations.AddField( - model_name='operation', - name='journal', - field=models.ForeignKey(verbose_name='journal', to='accounting.GeneralJournal', related_name='operations'), + model_name="operation", + name="journal", + field=models.ForeignKey( + verbose_name="journal", + to="accounting.GeneralJournal", + related_name="operations", + ), ), migrations.AddField( - model_name='operation', - name='linked_operation', - field=models.OneToOneField(blank=True, to='accounting.Operation', null=True, related_name='operation_linked_to', verbose_name='linked operation', default=None), + model_name="operation", + name="linked_operation", + field=models.OneToOneField( + blank=True, + to="accounting.Operation", + null=True, + related_name="operation_linked_to", + verbose_name="linked operation", + default=None, + ), ), migrations.AddField( - model_name='operation', - name='simpleaccounting_type', - field=models.ForeignKey(null=True, related_name='operations', verbose_name='simple type', to='accounting.SimplifiedAccountingType', blank=True), + model_name="operation", + name="simpleaccounting_type", + field=models.ForeignKey( + null=True, + related_name="operations", + verbose_name="simple type", + to="accounting.SimplifiedAccountingType", + blank=True, + ), ), migrations.AddField( - model_name='generaljournal', - name='club_account', - field=models.ForeignKey(verbose_name='club account', to='accounting.ClubAccount', related_name='journals'), + model_name="generaljournal", + name="club_account", + field=models.ForeignKey( + verbose_name="club account", + to="accounting.ClubAccount", + related_name="journals", + ), ), migrations.AddField( - model_name='clubaccount', - name='bank_account', - field=models.ForeignKey(verbose_name='bank account', to='accounting.BankAccount', related_name='club_accounts'), + model_name="clubaccount", + name="bank_account", + field=models.ForeignKey( + verbose_name="bank account", + to="accounting.BankAccount", + related_name="club_accounts", + ), ), migrations.AddField( - model_name='clubaccount', - name='club', - field=models.ForeignKey(verbose_name='club', to='club.Club', related_name='club_account'), + model_name="clubaccount", + name="club", + field=models.ForeignKey( + verbose_name="club", to="club.Club", related_name="club_account" + ), ), migrations.AddField( - model_name='bankaccount', - name='club', - field=models.ForeignKey(verbose_name='club', to='club.Club', related_name='bank_accounts'), + model_name="bankaccount", + name="club", + field=models.ForeignKey( + verbose_name="club", to="club.Club", related_name="bank_accounts" + ), ), migrations.AlterUniqueTogether( - name='operation', - unique_together=set([('number', 'journal')]), + name="operation", unique_together=set([("number", "journal")]) ), ] diff --git a/accounting/migrations/0003_auto_20160824_2203.py b/accounting/migrations/0003_auto_20160824_2203.py index 57bd9e22..47ae232f 100644 --- a/accounting/migrations/0003_auto_20160824_2203.py +++ b/accounting/migrations/0003_auto_20160824_2203.py @@ -7,44 +7,44 @@ import phonenumber_field.modelfields class Migration(migrations.Migration): - dependencies = [ - ('accounting', '0002_auto_20160824_2152'), - ] + dependencies = [("accounting", "0002_auto_20160824_2152")] operations = [ migrations.AddField( - model_name='company', - name='city', - field=models.CharField(blank=True, verbose_name='city', max_length=60), + model_name="company", + name="city", + field=models.CharField(blank=True, verbose_name="city", max_length=60), ), migrations.AddField( - model_name='company', - name='country', - field=models.CharField(blank=True, verbose_name='country', max_length=32), + model_name="company", + name="country", + field=models.CharField(blank=True, verbose_name="country", max_length=32), ), migrations.AddField( - model_name='company', - name='email', - field=models.EmailField(blank=True, verbose_name='email', max_length=254), + model_name="company", + name="email", + field=models.EmailField(blank=True, verbose_name="email", max_length=254), ), migrations.AddField( - model_name='company', - name='phone', - field=phonenumber_field.modelfields.PhoneNumberField(blank=True, verbose_name='phone', max_length=128), + model_name="company", + name="phone", + field=phonenumber_field.modelfields.PhoneNumberField( + blank=True, verbose_name="phone", max_length=128 + ), ), migrations.AddField( - model_name='company', - name='postcode', - field=models.CharField(blank=True, verbose_name='postcode', max_length=10), + model_name="company", + name="postcode", + field=models.CharField(blank=True, verbose_name="postcode", max_length=10), ), migrations.AddField( - model_name='company', - name='street', - field=models.CharField(blank=True, verbose_name='street', max_length=60), + model_name="company", + name="street", + field=models.CharField(blank=True, verbose_name="street", max_length=60), ), migrations.AddField( - model_name='company', - name='website', - field=models.CharField(blank=True, verbose_name='website', max_length=64), + model_name="company", + name="website", + field=models.CharField(blank=True, verbose_name="website", max_length=64), ), ] diff --git a/accounting/migrations/0004_auto_20161005_1505.py b/accounting/migrations/0004_auto_20161005_1505.py index b1bfe2d8..c363cddb 100644 --- a/accounting/migrations/0004_auto_20161005_1505.py +++ b/accounting/migrations/0004_auto_20161005_1505.py @@ -7,26 +7,45 @@ import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('accounting', '0003_auto_20160824_2203'), - ] + dependencies = [("accounting", "0003_auto_20160824_2203")] operations = [ migrations.CreateModel( - name='Label', + name="Label", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('name', models.CharField(max_length=64, verbose_name='label')), - ('club_account', models.ForeignKey(related_name='labels', verbose_name='club account', to='accounting.ClubAccount')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + auto_created=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="label")), + ( + "club_account", + models.ForeignKey( + related_name="labels", + verbose_name="club account", + to="accounting.ClubAccount", + ), + ), ], ), migrations.AddField( - model_name='operation', - name='label', - field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, related_name='operations', null=True, blank=True, verbose_name='label', to='accounting.Label'), + model_name="operation", + name="label", + field=models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + related_name="operations", + null=True, + blank=True, + verbose_name="label", + to="accounting.Label", + ), ), migrations.AlterUniqueTogether( - name='label', - unique_together=set([('name', 'club_account')]), + name="label", unique_together=set([("name", "club_account")]) ), ] diff --git a/accounting/migrations/0005_auto_20170324_0917.py b/accounting/migrations/0005_auto_20170324_0917.py index d97d82f0..c8258f8e 100644 --- a/accounting/migrations/0005_auto_20170324_0917.py +++ b/accounting/migrations/0005_auto_20170324_0917.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('accounting', '0004_auto_20161005_1505'), - ] + dependencies = [("accounting", "0004_auto_20161005_1505")] operations = [ migrations.AlterField( - model_name='operation', - name='remark', - field=models.CharField(null=True, max_length=128, blank=True, verbose_name='comment'), - ), + model_name="operation", + name="remark", + field=models.CharField( + null=True, max_length=128, blank=True, verbose_name="comment" + ), + ) ] diff --git a/accounting/models.py b/accounting/models.py index e5d73652..efcbdf2c 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -41,9 +41,10 @@ class CurrencyField(models.DecimalField): """ This is a custom database field used for currency """ + def __init__(self, *args, **kwargs): - kwargs['max_digits'] = 12 - kwargs['decimal_places'] = 2 + kwargs["max_digits"] = 12 + kwargs["decimal_places"] = 2 super(CurrencyField, self).__init__(*args, **kwargs) def to_python(self, value): @@ -52,18 +53,19 @@ class CurrencyField(models.DecimalField): except AttributeError: return None + # Accounting classes class Company(models.Model): - name = models.CharField(_('name'), max_length=60) - street = models.CharField(_('street'), max_length=60, blank=True) - city = models.CharField(_('city'), max_length=60, blank=True) - postcode = models.CharField(_('postcode'), max_length=10, blank=True) - country = models.CharField(_('country'), max_length=32, blank=True) - phone = PhoneNumberField(_('phone'), blank=True) - email = models.EmailField(_('email'), blank=True) - website = models.CharField(_('website'), max_length=64, blank=True) + name = models.CharField(_("name"), max_length=60) + street = models.CharField(_("street"), max_length=60, blank=True) + city = models.CharField(_("city"), max_length=60, blank=True) + postcode = models.CharField(_("postcode"), max_length=10, blank=True) + country = models.CharField(_("country"), max_length=32, blank=True) + phone = PhoneNumberField(_("phone"), blank=True) + email = models.EmailField(_("email"), blank=True) + website = models.CharField(_("website"), max_length=64, blank=True) class Meta: verbose_name = _("company") @@ -81,7 +83,7 @@ class Company(models.Model): Method to see if that object can be edited by the given user """ for club in user.memberships.filter(end_date=None).all(): - if club and club.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: + if club and club.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -90,12 +92,12 @@ class Company(models.Model): Method to see if that object can be viewed by the given user """ for club in user.memberships.filter(end_date=None).all(): - if club and club.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if club and club.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False def get_absolute_url(self): - return reverse('accounting:co_edit', kwargs={'co_id': self.id}) + return reverse("accounting:co_edit", kwargs={"co_id": self.id}) def get_display_name(self): return self.name @@ -105,14 +107,14 @@ class Company(models.Model): class BankAccount(models.Model): - name = models.CharField(_('name'), max_length=30) - iban = models.CharField(_('iban'), max_length=255, blank=True) - number = models.CharField(_('account number'), max_length=255, blank=True) + name = models.CharField(_("name"), max_length=30) + iban = models.CharField(_("iban"), max_length=255, blank=True) + number = models.CharField(_("account number"), max_length=255, blank=True) club = models.ForeignKey(Club, related_name="bank_accounts", verbose_name=_("club")) class Meta: verbose_name = _("Bank account") - ordering = ['club', 'name'] + ordering = ["club", "name"] def is_owned_by(self, user): """ @@ -121,25 +123,27 @@ class BankAccount(models.Model): if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): return True m = self.club.get_membership_for(user) - if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False def get_absolute_url(self): - return reverse('accounting:bank_details', kwargs={'b_account_id': self.id}) + return reverse("accounting:bank_details", kwargs={"b_account_id": self.id}) def __str__(self): return self.name class ClubAccount(models.Model): - name = models.CharField(_('name'), max_length=30) + name = models.CharField(_("name"), max_length=30) club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club")) - bank_account = models.ForeignKey(BankAccount, related_name="club_accounts", verbose_name=_("bank account")) + bank_account = models.ForeignKey( + BankAccount, related_name="club_accounts", verbose_name=_("bank account") + ) class Meta: verbose_name = _("Club account") - ordering = ['bank_account', 'name'] + ordering = ["bank_account", "name"] def is_owned_by(self, user): """ @@ -154,7 +158,7 @@ class ClubAccount(models.Model): Method to see if that object can be edited by the given user """ m = self.club.get_membership_for(user) - if m and m.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -163,7 +167,7 @@ class ClubAccount(models.Model): Method to see if that object can be viewed by the given user """ m = self.club.get_membership_for(user) - if m and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -177,30 +181,36 @@ class ClubAccount(models.Model): return self.journals.filter(closed=False).first() def get_absolute_url(self): - return reverse('accounting:club_details', kwargs={'c_account_id': self.id}) + return reverse("accounting:club_details", kwargs={"c_account_id": self.id}) def __str__(self): return self.name def get_display_name(self): - return _("%(club_account)s on %(bank_account)s") % {"club_account": self.name, "bank_account": self.bank_account} + return _("%(club_account)s on %(bank_account)s") % { + "club_account": self.name, + "bank_account": self.bank_account, + } class GeneralJournal(models.Model): """ Class storing all the operations for a period of time """ - start_date = models.DateField(_('start date')) - end_date = models.DateField(_('end date'), null=True, blank=True, default=None) - name = models.CharField(_('name'), max_length=40) - closed = models.BooleanField(_('is closed'), default=False) - club_account = models.ForeignKey(ClubAccount, related_name="journals", null=False, verbose_name=_("club account")) - amount = CurrencyField(_('amount'), default=0) - effective_amount = CurrencyField(_('effective_amount'), default=0) + + start_date = models.DateField(_("start date")) + end_date = models.DateField(_("end date"), null=True, blank=True, default=None) + name = models.CharField(_("name"), max_length=40) + closed = models.BooleanField(_("is closed"), default=False) + club_account = models.ForeignKey( + ClubAccount, related_name="journals", null=False, verbose_name=_("club account") + ) + amount = CurrencyField(_("amount"), default=0) + effective_amount = CurrencyField(_("effective_amount"), default=0) class Meta: verbose_name = _("General journal") - ordering = ['-start_date'] + ordering = ["-start_date"] def is_owned_by(self, user): """ @@ -226,7 +236,7 @@ class GeneralJournal(models.Model): return self.club_account.can_be_viewed_by(user) def get_absolute_url(self): - return reverse('accounting:journal_details', kwargs={'j_id': self.id}) + return reverse("accounting:journal_details", kwargs={"j_id": self.id}) def __str__(self): return self.name @@ -250,31 +260,79 @@ class Operation(models.Model): """ An operation is a line in the journal, a debit or a credit """ - number = models.IntegerField(_('number')) - journal = models.ForeignKey(GeneralJournal, related_name="operations", null=False, verbose_name=_("journal")) - amount = CurrencyField(_('amount')) - date = models.DateField(_('date')) - remark = models.CharField(_('comment'), max_length=128, null=True, blank=True) - mode = models.CharField(_('payment method'), max_length=255, choices=settings.SITH_ACCOUNTING_PAYMENT_METHOD) - cheque_number = models.CharField(_('cheque number'), max_length=32, default="", null=True, blank=True) - invoice = models.ForeignKey(SithFile, related_name='operations', verbose_name=_("invoice"), null=True, blank=True) - done = models.BooleanField(_('is done'), default=False) - simpleaccounting_type = models.ForeignKey('SimplifiedAccountingType', related_name="operations", - verbose_name=_("simple type"), null=True, blank=True) - accounting_type = models.ForeignKey('AccountingType', related_name="operations", - verbose_name=_("accounting type"), null=True, blank=True) - label = models.ForeignKey('Label', related_name="operations", - verbose_name=_("label"), null=True, blank=True, on_delete=models.SET_NULL) - target_type = models.CharField(_('target type'), max_length=10, - choices=[('USER', _('User')), ('CLUB', _('Club')), ('ACCOUNT', _('Account')), ('COMPANY', _('Company')), ('OTHER', _('Other'))]) - target_id = models.IntegerField(_('target id'), null=True, blank=True) - target_label = models.CharField(_('target label'), max_length=32, default="", blank=True) - linked_operation = models.OneToOneField('self', related_name='operation_linked_to', verbose_name=_("linked operation"), - null=True, blank=True, default=None) + + number = models.IntegerField(_("number")) + journal = models.ForeignKey( + GeneralJournal, related_name="operations", null=False, verbose_name=_("journal") + ) + amount = CurrencyField(_("amount")) + date = models.DateField(_("date")) + remark = models.CharField(_("comment"), max_length=128, null=True, blank=True) + mode = models.CharField( + _("payment method"), + max_length=255, + choices=settings.SITH_ACCOUNTING_PAYMENT_METHOD, + ) + cheque_number = models.CharField( + _("cheque number"), max_length=32, default="", null=True, blank=True + ) + invoice = models.ForeignKey( + SithFile, + related_name="operations", + verbose_name=_("invoice"), + null=True, + blank=True, + ) + done = models.BooleanField(_("is done"), default=False) + simpleaccounting_type = models.ForeignKey( + "SimplifiedAccountingType", + related_name="operations", + verbose_name=_("simple type"), + null=True, + blank=True, + ) + accounting_type = models.ForeignKey( + "AccountingType", + related_name="operations", + verbose_name=_("accounting type"), + null=True, + blank=True, + ) + label = models.ForeignKey( + "Label", + related_name="operations", + verbose_name=_("label"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + target_type = models.CharField( + _("target type"), + max_length=10, + choices=[ + ("USER", _("User")), + ("CLUB", _("Club")), + ("ACCOUNT", _("Account")), + ("COMPANY", _("Company")), + ("OTHER", _("Other")), + ], + ) + target_id = models.IntegerField(_("target id"), null=True, blank=True) + target_label = models.CharField( + _("target label"), max_length=32, default="", blank=True + ) + linked_operation = models.OneToOneField( + "self", + related_name="operation_linked_to", + verbose_name=_("linked operation"), + null=True, + blank=True, + default=None, + ) class Meta: - unique_together = ('number', 'journal') - ordering = ['-number'] + unique_together = ("number", "journal") + ordering = ["-number"] def __getattribute__(self, attr): if attr == "target": @@ -287,14 +345,29 @@ class Operation(models.Model): if self.date is None: raise ValidationError(_("The date must be set.")) elif self.date < self.journal.start_date: - raise ValidationError(_("""The date can not be before the start date of the journal, which is -%(start_date)s.""") % {'start_date': defaultfilters.date(self.journal.start_date, settings.DATE_FORMAT)}) + raise ValidationError( + _( + """The date can not be before the start date of the journal, which is +%(start_date)s.""" + ) + % { + "start_date": defaultfilters.date( + self.journal.start_date, settings.DATE_FORMAT + ) + } + ) if self.target_type != "OTHER" and self.get_target() is None: raise ValidationError(_("Target does not exists")) if self.target_type == "OTHER" and self.target_label == "": - raise ValidationError(_("Please add a target label if you set no existing target")) + raise ValidationError( + _("Please add a target label if you set no existing target") + ) if not self.accounting_type and not self.simpleaccounting_type: - raise ValidationError(_("You need to provide ether a simplified accounting type or a standard accounting type")) + raise ValidationError( + _( + "You need to provide ether a simplified accounting type or a standard accounting type" + ) + ) if self.simpleaccounting_type: self.accounting_type = self.simpleaccounting_type.accounting_type @@ -329,7 +402,7 @@ class Operation(models.Model): if self.journal.closed: return False m = self.journal.club_account.club.get_membership_for(user) - if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -342,16 +415,19 @@ class Operation(models.Model): if self.journal.closed: return False m = self.journal.club_account.club.get_membership_for(user) - if m is not None and m.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False def get_absolute_url(self): - return reverse('accounting:journal_details', kwargs={'j_id': self.journal.id}) + return reverse("accounting:journal_details", kwargs={"j_id": self.journal.id}) def __str__(self): return "%d € | %s | %s | %s" % ( - self.amount, self.date, self.accounting_type, self.done, + self.amount, + self.date, + self.accounting_type, + self.done, ) @@ -361,18 +437,30 @@ class AccountingType(models.Model): Thoses are numbers used in accounting to classify operations """ - code = models.CharField(_('code'), max_length=16, - validators=[ - validators.RegexValidator(r'^[0-9]*$', _('An accounting type code contains only numbers')), - ], + + code = models.CharField( + _("code"), + max_length=16, + validators=[ + validators.RegexValidator( + r"^[0-9]*$", _("An accounting type code contains only numbers") + ) + ], + ) + label = models.CharField(_("label"), max_length=128) + movement_type = models.CharField( + _("movement type"), + choices=[ + ("CREDIT", _("Credit")), + ("DEBIT", _("Debit")), + ("NEUTRAL", _("Neutral")), + ], + max_length=12, ) - label = models.CharField(_('label'), max_length=128) - movement_type = models.CharField(_('movement type'), choices=[('CREDIT', _('Credit')), ('DEBIT', _('Debit')), - ('NEUTRAL', _('Neutral'))], max_length=12) class Meta: verbose_name = _("accounting type") - ordering = ['movement_type', 'code'] + ordering = ["movement_type", "code"] def is_owned_by(self, user): """ @@ -383,7 +471,7 @@ class AccountingType(models.Model): return False def get_absolute_url(self): - return reverse('accounting:type_list') + return reverse("accounting:type_list") def __str__(self): return self.code + " - " + self.get_movement_type_display() + " - " + self.label @@ -393,13 +481,17 @@ class SimplifiedAccountingType(models.Model): """ Class describing the simplified accounting types. """ - label = models.CharField(_('label'), max_length=128) - accounting_type = models.ForeignKey(AccountingType, related_name="simplified_types", - verbose_name=_("simplified accounting types")) + + label = models.CharField(_("label"), max_length=128) + accounting_type = models.ForeignKey( + AccountingType, + related_name="simplified_types", + verbose_name=_("simplified accounting types"), + ) class Meta: verbose_name = _("simplified type") - ordering = ['accounting_type__movement_type', 'accounting_type__code'] + ordering = ["accounting_type__movement_type", "accounting_type__code"] @property def movement_type(self): @@ -409,25 +501,36 @@ class SimplifiedAccountingType(models.Model): return self.accounting_type.get_movement_type_display() def get_absolute_url(self): - return reverse('accounting:simple_type_list') + return reverse("accounting:simple_type_list") def __str__(self): - return self.get_movement_type_display() + " - " + self.accounting_type.code + " - " + self.label + return ( + self.get_movement_type_display() + + " - " + + self.accounting_type.code + + " - " + + self.label + ) class Label(models.Model): """Label allow a club to sort its operations""" - name = models.CharField(_('label'), max_length=64) - club_account = models.ForeignKey(ClubAccount, related_name="labels", verbose_name=_("club account")) + + name = models.CharField(_("label"), max_length=64) + club_account = models.ForeignKey( + ClubAccount, related_name="labels", verbose_name=_("club account") + ) class Meta: - unique_together = ('name', 'club_account') + unique_together = ("name", "club_account") def __str__(self): return "%s (%s)" % (self.name, self.club_account.name) def get_absolute_url(self): - return reverse('accounting:label_list', kwargs={'clubaccount_id': self.club_account.id}) + return reverse( + "accounting:label_list", kwargs={"clubaccount_id": self.club_account.id} + ) def is_owned_by(self, user): return self.club_account.is_owned_by(user) diff --git a/accounting/tests.py b/accounting/tests.py index ceb31c29..df4fd8d7 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -28,7 +28,13 @@ from django.core.management import call_command from datetime import date from core.models import User -from accounting.models import GeneralJournal, Operation, Label, AccountingType, SimplifiedAccountingType +from accounting.models import ( + GeneralJournal, + Operation, + Label, + AccountingType, + SimplifiedAccountingType, +) class RefoundAccountTest(TestCase): @@ -40,18 +46,20 @@ class RefoundAccountTest(TestCase): self.skia.customer.save() def test_permission_denied(self): - self.client.login(username='guy', password='plop') - response_post = self.client.post(reverse("accounting:refound_account"), - {"user": self.skia.id}) + self.client.login(username="guy", password="plop") + response_post = self.client.post( + reverse("accounting:refound_account"), {"user": self.skia.id} + ) response_get = self.client.get(reverse("accounting:refound_account")) self.assertTrue(response_get.status_code == 403) self.assertTrue(response_post.status_code == 403) def test_root_granteed(self): - self.client.login(username='root', password='plop') - response_post = self.client.post(reverse("accounting:refound_account"), - {"user": self.skia.id}) - self.skia = User.objects.filter(username='skia').first() + self.client.login(username="root", password="plop") + response_post = self.client.post( + reverse("accounting:refound_account"), {"user": self.skia.id} + ) + self.skia = User.objects.filter(username="skia").first() response_get = self.client.get(reverse("accounting:refound_account")) self.assertFalse(response_get.status_code == 403) self.assertTrue('
' in str(response_get.content)) @@ -59,10 +67,11 @@ class RefoundAccountTest(TestCase): self.assertTrue(self.skia.customer.amount == 0) def test_comptable_granteed(self): - self.client.login(username='comptable', password='plop') - response_post = self.client.post(reverse("accounting:refound_account"), - {"user": self.skia.id}) - self.skia = User.objects.filter(username='skia').first() + self.client.login(username="comptable", password="plop") + response_post = self.client.post( + reverse("accounting:refound_account"), {"user": self.skia.id} + ) + self.skia = User.objects.filter(username="skia").first() response_get = self.client.get(reverse("accounting:refound_account")) self.assertFalse(response_get.status_code == 403) self.assertTrue('' in str(response_get.content)) @@ -76,146 +85,217 @@ class JournalTest(TestCase): self.journal = GeneralJournal.objects.filter(id=1).first() def test_permission_granted(self): - self.client.login(username='comptable', password='plop') - response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) + self.client.login(username="comptable", password="plop") + response_get = self.client.get( + reverse("accounting:journal_details", args=[self.journal.id]) + ) self.assertTrue(response_get.status_code == 200) - self.assertTrue('M\\xc3\\xa9thode de paiement' in str(response_get.content)) + self.assertTrue( + "M\\xc3\\xa9thode de paiement" in str(response_get.content) + ) def test_permission_not_granted(self): - self.client.login(username='skia', password='plop') - response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) + self.client.login(username="skia", password="plop") + response_get = self.client.get( + reverse("accounting:journal_details", args=[self.journal.id]) + ) self.assertTrue(response_get.status_code == 403) - self.assertFalse('M\xc3\xa9thode de paiement' in str(response_get.content)) + self.assertFalse( + "M\xc3\xa9thode de paiement" in str(response_get.content) + ) class OperationTest(TestCase): def setUp(self): call_command("populate") self.journal = GeneralJournal.objects.filter(id=1).first() - self.skia = User.objects.filter(username='skia').first() - at = AccountingType(code='443', label="Ce code n'existe pas", movement_type='CREDIT') + self.skia = User.objects.filter(username="skia").first() + at = AccountingType( + code="443", label="Ce code n'existe pas", movement_type="CREDIT" + ) at.save() - l = Label(club_account=self.journal.club_account, name='bob') + l = Label(club_account=self.journal.club_account, name="bob") l.save() - self.client.login(username='comptable', password='plop') - self.op1 = Operation(journal=self.journal, date=date.today(), amount=1, - remark="Test bilan", mode='CASH', done=True, label=l, - accounting_type=at, target_type='USER', target_id=self.skia.id) + self.client.login(username="comptable", password="plop") + self.op1 = Operation( + journal=self.journal, + date=date.today(), + amount=1, + remark="Test bilan", + mode="CASH", + done=True, + label=l, + accounting_type=at, + target_type="USER", + target_id=self.skia.id, + ) self.op1.save() - self.op2 = Operation(journal=self.journal, date=date.today(), amount=2, - remark="Test bilan", mode='CASH', done=True, label=l, - accounting_type=at, target_type='USER', target_id=self.skia.id) + self.op2 = Operation( + journal=self.journal, + date=date.today(), + amount=2, + remark="Test bilan", + mode="CASH", + done=True, + label=l, + accounting_type=at, + target_type="USER", + target_id=self.skia.id, + ) self.op2.save() def test_new_operation(self): - self.client.login(username='comptable', password='plop') - at = AccountingType.objects.filter(code='604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark': "Un gros test", - 'journal': self.journal.id, - 'target_type': 'OTHER', - 'target_id': '', - 'target_label': "Le fantome de la nuit", - 'date': '04/12/2020', - 'mode': 'CASH', - 'cheque_number': '', - 'invoice': '', - 'simpleaccounting_type': '', - 'accounting_type': at.id, - 'label': '', - 'done': False, - }) + self.client.login(username="comptable", password="plop") + at = AccountingType.objects.filter(code="604").first() + response = self.client.post( + reverse("accounting:op_new", args=[self.journal.id]), + { + "amount": 30, + "remark": "Un gros test", + "journal": self.journal.id, + "target_type": "OTHER", + "target_id": "", + "target_label": "Le fantome de la nuit", + "date": "04/12/2020", + "mode": "CASH", + "cheque_number": "", + "invoice": "", + "simpleaccounting_type": "", + "accounting_type": at.id, + "label": "", + "done": False, + }, + ) self.assertFalse(response.status_code == 403) - self.assertTrue(self.journal.operations.filter(target_label="Le fantome de la nuit").exists()) - response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) - self.assertTrue('Le fantome de la nuit' in str(response_get.content)) + self.assertTrue( + self.journal.operations.filter( + target_label="Le fantome de la nuit" + ).exists() + ) + response_get = self.client.get( + reverse("accounting:journal_details", args=[self.journal.id]) + ) + self.assertTrue("Le fantome de la nuit" in str(response_get.content)) def test_bad_new_operation(self): - self.client.login(username='comptable', password='plop') - AccountingType.objects.filter(code='604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark': "Un gros test", - 'journal': self.journal.id, - 'target_type': 'OTHER', - 'target_id': '', - 'target_label': "Le fantome de la nuit", - 'date': '04/12/2020', - 'mode': 'CASH', - 'cheque_number': '', - 'invoice': '', - 'simpleaccounting_type': '', - 'accounting_type': '', - 'label': '', - 'done': False, - }) - self.assertTrue('Vous devez fournir soit un type comptable simplifi\\xc3\\xa9 ou un type comptable standard' in str(response.content)) + self.client.login(username="comptable", password="plop") + AccountingType.objects.filter(code="604").first() + response = self.client.post( + reverse("accounting:op_new", args=[self.journal.id]), + { + "amount": 30, + "remark": "Un gros test", + "journal": self.journal.id, + "target_type": "OTHER", + "target_id": "", + "target_label": "Le fantome de la nuit", + "date": "04/12/2020", + "mode": "CASH", + "cheque_number": "", + "invoice": "", + "simpleaccounting_type": "", + "accounting_type": "", + "label": "", + "done": False, + }, + ) + self.assertTrue( + "Vous devez fournir soit un type comptable simplifi\\xc3\\xa9 ou un type comptable standard" + in str(response.content) + ) def test_new_operation_not_authorized(self): - self.client.login(username='skia', password='plop') - at = AccountingType.objects.filter(code='604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark': "Un gros test", - 'journal': self.journal.id, - 'target_type': 'OTHER', - 'target_id': '', - 'target_label': "Le fantome du jour", - 'date': '04/12/2020', - 'mode': 'CASH', - 'cheque_number': '', - 'invoice': '', - 'simpleaccounting_type': '', - 'accounting_type': at.id, - 'label': '', - 'done': False, - }) + self.client.login(username="skia", password="plop") + at = AccountingType.objects.filter(code="604").first() + response = self.client.post( + reverse("accounting:op_new", args=[self.journal.id]), + { + "amount": 30, + "remark": "Un gros test", + "journal": self.journal.id, + "target_type": "OTHER", + "target_id": "", + "target_label": "Le fantome du jour", + "date": "04/12/2020", + "mode": "CASH", + "cheque_number": "", + "invoice": "", + "simpleaccounting_type": "", + "accounting_type": at.id, + "label": "", + "done": False, + }, + ) self.assertTrue(response.status_code == 403) - self.assertFalse(self.journal.operations.filter(target_label="Le fantome du jour").exists()) + self.assertFalse( + self.journal.operations.filter(target_label="Le fantome du jour").exists() + ) def test__operation_simple_accounting(self): - self.client.login(username='comptable', password='plop') + self.client.login(username="comptable", password="plop") sat = SimplifiedAccountingType.objects.all().first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 23, - 'remark': "Un gros test", - 'journal': self.journal.id, - 'target_type': 'OTHER', - 'target_id': '', - 'target_label': "Le fantome de l'aurore", - 'date': '04/12/2020', - 'mode': 'CASH', - 'cheque_number': '', - 'invoice': '', - 'simpleaccounting_type': sat.id, - 'accounting_type': '', - 'label': '', - 'done': False, - }) + response = self.client.post( + reverse("accounting:op_new", args=[self.journal.id]), + { + "amount": 23, + "remark": "Un gros test", + "journal": self.journal.id, + "target_type": "OTHER", + "target_id": "", + "target_label": "Le fantome de l'aurore", + "date": "04/12/2020", + "mode": "CASH", + "cheque_number": "", + "invoice": "", + "simpleaccounting_type": sat.id, + "accounting_type": "", + "label": "", + "done": False, + }, + ) self.assertFalse(response.status_code == 403) self.assertTrue(self.journal.operations.filter(amount=23).exists()) - response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) - self.assertTrue("Le fantome de l'aurore" in str(response_get.content)) - self.assertTrue(self.journal.operations.filter(amount=23).values('accounting_type').first()['accounting_type'] == AccountingType.objects.filter(code=6).values('id').first()['id']) + response_get = self.client.get( + reverse("accounting:journal_details", args=[self.journal.id]) + ) + self.assertTrue( + "Le fantome de l'aurore" in str(response_get.content) + ) + self.assertTrue( + self.journal.operations.filter(amount=23) + .values("accounting_type") + .first()["accounting_type"] + == AccountingType.objects.filter(code=6).values("id").first()["id"] + ) def test_nature_statement(self): - self.client.login(username='comptable', password='plop') - response_get = self.client.get(reverse("accounting:journal_nature_statement", args=[self.journal.id])) - self.assertTrue("bob (Troll Pench\\xc3\\xa9) : 3.00" in str(response_get.content)) + self.client.login(username="comptable", password="plop") + response_get = self.client.get( + reverse("accounting:journal_nature_statement", args=[self.journal.id]) + ) + self.assertTrue( + "bob (Troll Pench\\xc3\\xa9) : 3.00" in str(response_get.content) + ) def test_person_statement(self): - self.client.login(username='comptable', password='plop') - response_get = self.client.get(reverse("accounting:journal_person_statement", args=[self.journal.id])) - self.assertTrue("3.00" in str(response_get.content) and 'S' Kia' in str(response_get.content)) + self.client.login(username="comptable", password="plop") + response_get = self.client.get( + reverse("accounting:journal_person_statement", args=[self.journal.id]) + ) + self.assertTrue( + "3.00" in str(response_get.content) + and 'S' Kia' + in str(response_get.content) + ) def test_accounting_statement(self): - self.client.login(username='comptable', password='plop') - response_get = self.client.get(reverse("accounting:journal_accounting_statement", args=[self.journal.id])) - self.assertTrue("443 - Cr\\xc3\\xa9dit - Ce code n'existe pas" in str(response_get.content)) + self.client.login(username="comptable", password="plop") + response_get = self.client.get( + reverse("accounting:journal_accounting_statement", args=[self.journal.id]) + ) + self.assertTrue( + "443 - Cr\\xc3\\xa9dit - Ce code n'existe pas" + in str(response_get.content) + ) diff --git a/accounting/urls.py b/accounting/urls.py index 70e6a3ea..63882d45 100644 --- a/accounting/urls.py +++ b/accounting/urls.py @@ -28,46 +28,125 @@ from accounting.views import * urlpatterns = [ # Accounting types - url(r'^simple_type$', SimplifiedAccountingTypeListView.as_view(), name='simple_type_list'), - url(r'^simple_type/create$', SimplifiedAccountingTypeCreateView.as_view(), name='simple_type_new'), - url(r'^simple_type/(?P[0-9]+)/edit$', SimplifiedAccountingTypeEditView.as_view(), name='simple_type_edit'), + url( + r"^simple_type$", + SimplifiedAccountingTypeListView.as_view(), + name="simple_type_list", + ), + url( + r"^simple_type/create$", + SimplifiedAccountingTypeCreateView.as_view(), + name="simple_type_new", + ), + url( + r"^simple_type/(?P[0-9]+)/edit$", + SimplifiedAccountingTypeEditView.as_view(), + name="simple_type_edit", + ), # Accounting types - url(r'^type$', AccountingTypeListView.as_view(), name='type_list'), - url(r'^type/create$', AccountingTypeCreateView.as_view(), name='type_new'), - url(r'^type/(?P[0-9]+)/edit$', AccountingTypeEditView.as_view(), name='type_edit'), + url(r"^type$", AccountingTypeListView.as_view(), name="type_list"), + url(r"^type/create$", AccountingTypeCreateView.as_view(), name="type_new"), + url( + r"^type/(?P[0-9]+)/edit$", + AccountingTypeEditView.as_view(), + name="type_edit", + ), # Bank accounts - url(r'^$', BankAccountListView.as_view(), name='bank_list'), - url(r'^bank/create$', BankAccountCreateView.as_view(), name='bank_new'), - url(r'^bank/(?P[0-9]+)$', BankAccountDetailView.as_view(), name='bank_details'), - url(r'^bank/(?P[0-9]+)/edit$', BankAccountEditView.as_view(), name='bank_edit'), - url(r'^bank/(?P[0-9]+)/delete$', BankAccountDeleteView.as_view(), name='bank_delete'), + url(r"^$", BankAccountListView.as_view(), name="bank_list"), + url(r"^bank/create$", BankAccountCreateView.as_view(), name="bank_new"), + url( + r"^bank/(?P[0-9]+)$", + BankAccountDetailView.as_view(), + name="bank_details", + ), + url( + r"^bank/(?P[0-9]+)/edit$", + BankAccountEditView.as_view(), + name="bank_edit", + ), + url( + r"^bank/(?P[0-9]+)/delete$", + BankAccountDeleteView.as_view(), + name="bank_delete", + ), # Club accounts - url(r'^club/create$', ClubAccountCreateView.as_view(), name='club_new'), - url(r'^club/(?P[0-9]+)$', ClubAccountDetailView.as_view(), name='club_details'), - url(r'^club/(?P[0-9]+)/edit$', ClubAccountEditView.as_view(), name='club_edit'), - url(r'^club/(?P[0-9]+)/delete$', ClubAccountDeleteView.as_view(), name='club_delete'), + url(r"^club/create$", ClubAccountCreateView.as_view(), name="club_new"), + url( + r"^club/(?P[0-9]+)$", + ClubAccountDetailView.as_view(), + name="club_details", + ), + url( + r"^club/(?P[0-9]+)/edit$", + ClubAccountEditView.as_view(), + name="club_edit", + ), + url( + r"^club/(?P[0-9]+)/delete$", + ClubAccountDeleteView.as_view(), + name="club_delete", + ), # Journals - url(r'^journal/create$', JournalCreateView.as_view(), name='journal_new'), - url(r'^journal/(?P[0-9]+)$', JournalDetailView.as_view(), name='journal_details'), - url(r'^journal/(?P[0-9]+)/edit$', JournalEditView.as_view(), name='journal_edit'), - url(r'^journal/(?P[0-9]+)/delete$', JournalDeleteView.as_view(), name='journal_delete'), - url(r'^journal/(?P[0-9]+)/statement/nature$', JournalNatureStatementView.as_view(), name='journal_nature_statement'), - url(r'^journal/(?P[0-9]+)/statement/person$', JournalPersonStatementView.as_view(), name='journal_person_statement'), - url(r'^journal/(?P[0-9]+)/statement/accounting$', JournalAccountingStatementView.as_view(), name='journal_accounting_statement'), - + url(r"^journal/create$", JournalCreateView.as_view(), name="journal_new"), + url( + r"^journal/(?P[0-9]+)$", + JournalDetailView.as_view(), + name="journal_details", + ), + url( + r"^journal/(?P[0-9]+)/edit$", + JournalEditView.as_view(), + name="journal_edit", + ), + url( + r"^journal/(?P[0-9]+)/delete$", + JournalDeleteView.as_view(), + name="journal_delete", + ), + url( + r"^journal/(?P[0-9]+)/statement/nature$", + JournalNatureStatementView.as_view(), + name="journal_nature_statement", + ), + url( + r"^journal/(?P[0-9]+)/statement/person$", + JournalPersonStatementView.as_view(), + name="journal_person_statement", + ), + url( + r"^journal/(?P[0-9]+)/statement/accounting$", + JournalAccountingStatementView.as_view(), + name="journal_accounting_statement", + ), # Operations - url(r'^operation/create/(?P[0-9]+)$', OperationCreateView.as_view(), name='op_new'), - url(r'^operation/(?P[0-9]+)$', OperationEditView.as_view(), name='op_edit'), - url(r'^operation/(?P[0-9]+)/pdf$', OperationPDFView.as_view(), name='op_pdf'), + url( + r"^operation/create/(?P[0-9]+)$", + OperationCreateView.as_view(), + name="op_new", + ), + url(r"^operation/(?P[0-9]+)$", OperationEditView.as_view(), name="op_edit"), + url( + r"^operation/(?P[0-9]+)/pdf$", OperationPDFView.as_view(), name="op_pdf" + ), # Companies - url(r'^company/list$', CompanyListView.as_view(), name='co_list'), - url(r'^company/create$', CompanyCreateView.as_view(), name='co_new'), - url(r'^company/(?P[0-9]+)$', CompanyEditView.as_view(), name='co_edit'), + url(r"^company/list$", CompanyListView.as_view(), name="co_list"), + url(r"^company/create$", CompanyCreateView.as_view(), name="co_new"), + url(r"^company/(?P[0-9]+)$", CompanyEditView.as_view(), name="co_edit"), # Labels - url(r'^label/new$', LabelCreateView.as_view(), name='label_new'), - url(r'^label/(?P[0-9]+)$', LabelListView.as_view(), name='label_list'), - url(r'^label/(?P[0-9]+)/edit$', LabelEditView.as_view(), name='label_edit'), - url(r'^label/(?P[0-9]+)/delete$', LabelDeleteView.as_view(), name='label_delete'), + url(r"^label/new$", LabelCreateView.as_view(), name="label_new"), + url( + r"^label/(?P[0-9]+)$", + LabelListView.as_view(), + name="label_list", + ), + url( + r"^label/(?P[0-9]+)/edit$", LabelEditView.as_view(), name="label_edit" + ), + url( + r"^label/(?P[0-9]+)/delete$", + LabelDeleteView.as_view(), + name="label_delete", + ), # User account - url(r'^refound/account$', RefoundAccountView.as_view(), name='refound_account'), + url(r"^refound/account$", RefoundAccountView.as_view(), name="refound_account"), ] diff --git a/accounting/views.py b/accounting/views.py index 3de31c99..5ebd0e2a 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -38,9 +38,24 @@ import collections from ajax_select.fields import AutoCompleteSelectField -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin +from core.views import ( + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + CanCreateMixin, + TabedViewMixin, +) from core.views.forms import SelectFile, SelectDate -from accounting.models import BankAccount, ClubAccount, GeneralJournal, Operation, AccountingType, Company, SimplifiedAccountingType, Label +from accounting.models import ( + BankAccount, + ClubAccount, + GeneralJournal, + Operation, + AccountingType, + Company, + SimplifiedAccountingType, + Label, +) from counter.models import Counter, Selling, Product # Main accounting view @@ -50,185 +65,228 @@ class BankAccountListView(CanViewMixin, ListView): """ A list view for the admins """ + model = BankAccount - template_name = 'accounting/bank_account_list.jinja' - ordering = ['name'] + template_name = "accounting/bank_account_list.jinja" + ordering = ["name"] # Simplified accounting types + class SimplifiedAccountingTypeListView(CanViewMixin, ListView): """ A list view for the admins """ + model = SimplifiedAccountingType - template_name = 'accounting/simplifiedaccountingtype_list.jinja' + template_name = "accounting/simplifiedaccountingtype_list.jinja" class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView): """ An edit view for the admins """ + model = SimplifiedAccountingType pk_url_kwarg = "type_id" - fields = ['label', 'accounting_type'] - template_name = 'core/edit.jinja' + fields = ["label", "accounting_type"] + template_name = "core/edit.jinja" class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView): """ Create an accounting type (for the admins) """ + model = SimplifiedAccountingType - fields = ['label', 'accounting_type'] - template_name = 'core/create.jinja' + fields = ["label", "accounting_type"] + template_name = "core/create.jinja" # Accounting types + class AccountingTypeListView(CanViewMixin, ListView): """ A list view for the admins """ + model = AccountingType - template_name = 'accounting/accountingtype_list.jinja' + template_name = "accounting/accountingtype_list.jinja" class AccountingTypeEditView(CanViewMixin, UpdateView): """ An edit view for the admins """ + model = AccountingType pk_url_kwarg = "type_id" - fields = ['code', 'label', 'movement_type'] - template_name = 'core/edit.jinja' + fields = ["code", "label", "movement_type"] + template_name = "core/edit.jinja" class AccountingTypeCreateView(CanCreateMixin, CreateView): """ Create an accounting type (for the admins) """ + model = AccountingType - fields = ['code', 'label', 'movement_type'] - template_name = 'core/create.jinja' + fields = ["code", "label", "movement_type"] + template_name = "core/create.jinja" # BankAccount views + class BankAccountEditView(CanViewMixin, UpdateView): """ An edit view for the admins """ + model = BankAccount pk_url_kwarg = "b_account_id" - fields = ['name', 'iban', 'number', 'club'] - template_name = 'core/edit.jinja' + fields = ["name", "iban", "number", "club"] + template_name = "core/edit.jinja" class BankAccountDetailView(CanViewMixin, DetailView): """ A detail view, listing every club account """ + model = BankAccount pk_url_kwarg = "b_account_id" - template_name = 'accounting/bank_account_details.jinja' + template_name = "accounting/bank_account_details.jinja" class BankAccountCreateView(CanCreateMixin, CreateView): """ Create a bank account (for the admins) """ + model = BankAccount - fields = ['name', 'club', 'iban', 'number'] - template_name = 'core/create.jinja' + fields = ["name", "club", "iban", "number"] + template_name = "core/create.jinja" -class BankAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close +class BankAccountDeleteView( + CanEditPropMixin, DeleteView +): # TODO change Delete to Close """ Delete a bank account (for the admins) """ + model = BankAccount pk_url_kwarg = "b_account_id" - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('accounting:bank_list') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("accounting:bank_list") # ClubAccount views + class ClubAccountEditView(CanViewMixin, UpdateView): """ An edit view for the admins """ + model = ClubAccount pk_url_kwarg = "c_account_id" - fields = ['name', 'club', 'bank_account'] - template_name = 'core/edit.jinja' + fields = ["name", "club", "bank_account"] + template_name = "core/edit.jinja" class ClubAccountDetailView(CanViewMixin, DetailView): """ A detail view, listing every journal """ + model = ClubAccount pk_url_kwarg = "c_account_id" - template_name = 'accounting/club_account_details.jinja' + template_name = "accounting/club_account_details.jinja" class ClubAccountCreateView(CanCreateMixin, CreateView): """ Create a club account (for the admins) """ + model = ClubAccount - fields = ['name', 'club', 'bank_account'] - template_name = 'core/create.jinja' + fields = ["name", "club", "bank_account"] + template_name = "core/create.jinja" def get_initial(self): ret = super(ClubAccountCreateView, self).get_initial() - if 'parent' in self.request.GET.keys(): - obj = BankAccount.objects.filter(id=int(self.request.GET['parent'])).first() + if "parent" in self.request.GET.keys(): + obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first() if obj is not None: - ret['bank_account'] = obj.id + ret["bank_account"] = obj.id return ret -class ClubAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close +class ClubAccountDeleteView( + CanEditPropMixin, DeleteView +): # TODO change Delete to Close """ Delete a club account (for the admins) """ + model = ClubAccount pk_url_kwarg = "c_account_id" - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('accounting:bank_list') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("accounting:bank_list") # Journal views + class JournalTabsMixin(TabedViewMixin): def get_tabs_title(self): return _("Journal") def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse('accounting:journal_details', kwargs={'j_id': self.object.id}), - 'slug': 'journal', - 'name': _("Journal"), - }) - tab_list.append({ - 'url': reverse('accounting:journal_nature_statement', kwargs={'j_id': self.object.id}), - 'slug': 'nature_statement', - 'name': _("Statement by nature"), - }) - tab_list.append({ - 'url': reverse('accounting:journal_person_statement', kwargs={'j_id': self.object.id}), - 'slug': 'person_statement', - 'name': _("Statement by person"), - }) - tab_list.append({ - 'url': reverse('accounting:journal_accounting_statement', kwargs={'j_id': self.object.id}), - 'slug': 'accounting_statement', - 'name': _("Accounting statement"), - }) + tab_list.append( + { + "url": reverse( + "accounting:journal_details", kwargs={"j_id": self.object.id} + ), + "slug": "journal", + "name": _("Journal"), + } + ) + tab_list.append( + { + "url": reverse( + "accounting:journal_nature_statement", + kwargs={"j_id": self.object.id}, + ), + "slug": "nature_statement", + "name": _("Statement by nature"), + } + ) + tab_list.append( + { + "url": reverse( + "accounting:journal_person_statement", + kwargs={"j_id": self.object.id}, + ), + "slug": "person_statement", + "name": _("Statement by person"), + } + ) + tab_list.append( + { + "url": reverse( + "accounting:journal_accounting_statement", + kwargs={"j_id": self.object.id}, + ), + "slug": "accounting_statement", + "name": _("Accounting statement"), + } + ) return tab_list @@ -236,17 +294,21 @@ class JournalCreateView(CanCreateMixin, CreateView): """ Create a general journal """ + model = GeneralJournal - form_class = modelform_factory(GeneralJournal, fields=['name', 'start_date', 'club_account'], - widgets={'start_date': SelectDate, }) - template_name = 'core/create.jinja' + form_class = modelform_factory( + GeneralJournal, + fields=["name", "start_date", "club_account"], + widgets={"start_date": SelectDate}, + ) + template_name = "core/create.jinja" def get_initial(self): ret = super(JournalCreateView, self).get_initial() - if 'parent' in self.request.GET.keys(): - obj = ClubAccount.objects.filter(id=int(self.request.GET['parent'])).first() + if "parent" in self.request.GET.keys(): + obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first() if obj is not None: - ret['club_account'] = obj.id + ret["club_account"] = obj.id return ret @@ -254,30 +316,33 @@ class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView): """ A detail view, listing every operation """ + model = GeneralJournal pk_url_kwarg = "j_id" - template_name = 'accounting/journal_details.jinja' - current_tab = 'journal' + template_name = "accounting/journal_details.jinja" + current_tab = "journal" class JournalEditView(CanEditMixin, UpdateView): """ Update a general journal """ + model = GeneralJournal pk_url_kwarg = "j_id" - fields = ['name', 'start_date', 'end_date', 'club_account', 'closed'] - template_name = 'core/edit.jinja' + fields = ["name", "start_date", "end_date", "club_account", "closed"] + template_name = "core/edit.jinja" class JournalDeleteView(CanEditPropMixin, DeleteView): """ Delete a club account (for the admins) """ + model = GeneralJournal pk_url_kwarg = "j_id" - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('accounting:club_details') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("accounting:club_details") def dispatch(self, request, *args, **kwargs): self.object = self.get_object() @@ -289,64 +354,105 @@ class JournalDeleteView(CanEditPropMixin, DeleteView): # Operation views + class OperationForm(forms.ModelForm): class Meta: model = Operation - fields = ['amount', 'remark', 'journal', 'target_type', 'target_id', 'target_label', 'date', 'mode', - 'cheque_number', 'invoice', 'simpleaccounting_type', 'accounting_type', 'label', 'done'] + fields = [ + "amount", + "remark", + "journal", + "target_type", + "target_id", + "target_label", + "date", + "mode", + "cheque_number", + "invoice", + "simpleaccounting_type", + "accounting_type", + "label", + "done", + ] widgets = { - 'journal': HiddenInput, - 'target_id': HiddenInput, - 'date': SelectDate, - 'invoice': SelectFile, + "journal": HiddenInput, + "target_id": HiddenInput, + "date": SelectDate, + "invoice": SelectFile, } - user = AutoCompleteSelectField('users', help_text=None, required=False) - club_account = AutoCompleteSelectField('club_accounts', help_text=None, required=False) - club = AutoCompleteSelectField('clubs', help_text=None, required=False) - company = AutoCompleteSelectField('companies', help_text=None, required=False) - need_link = forms.BooleanField(label=_("Link this operation to the target account"), required=False, initial=False) + + user = AutoCompleteSelectField("users", help_text=None, required=False) + club_account = AutoCompleteSelectField( + "club_accounts", help_text=None, required=False + ) + club = AutoCompleteSelectField("clubs", help_text=None, required=False) + company = AutoCompleteSelectField("companies", help_text=None, required=False) + need_link = forms.BooleanField( + label=_("Link this operation to the target account"), + required=False, + initial=False, + ) def __init__(self, *args, **kwargs): - club_account = kwargs.pop('club_account', None) + club_account = kwargs.pop("club_account", None) super(OperationForm, self).__init__(*args, **kwargs) if club_account: - self.fields['label'].queryset = club_account.labels.order_by('name').all() + self.fields["label"].queryset = club_account.labels.order_by("name").all() if self.instance.target_type == "USER": - self.fields['user'].initial = self.instance.target_id + self.fields["user"].initial = self.instance.target_id elif self.instance.target_type == "ACCOUNT": - self.fields['club_account'].initial = self.instance.target_id + self.fields["club_account"].initial = self.instance.target_id elif self.instance.target_type == "CLUB": - self.fields['club'].initial = self.instance.target_id + self.fields["club"].initial = self.instance.target_id elif self.instance.target_type == "COMPANY": - self.fields['company'].initial = self.instance.target_id + self.fields["company"].initial = self.instance.target_id def clean(self): self.cleaned_data = super(OperationForm, self).clean() - if 'target_type' in self.cleaned_data.keys(): - if self.cleaned_data.get("user") is None and self.cleaned_data.get("club") is None and self.cleaned_data.get("club_account") is None and self.cleaned_data.get("company") is None and self.cleaned_data.get("target_label") is None: - self.add_error('target_type', ValidationError(_("The target must be set."))) + if "target_type" in self.cleaned_data.keys(): + if ( + self.cleaned_data.get("user") is None + and self.cleaned_data.get("club") is None + and self.cleaned_data.get("club_account") is None + and self.cleaned_data.get("company") is None + and self.cleaned_data.get("target_label") is None + ): + self.add_error( + "target_type", ValidationError(_("The target must be set.")) + ) else: - if self.cleaned_data['target_type'] == "USER": - self.cleaned_data['target_id'] = self.cleaned_data['user'].id - elif self.cleaned_data['target_type'] == "ACCOUNT": - self.cleaned_data['target_id'] = self.cleaned_data['club_account'].id - elif self.cleaned_data['target_type'] == "CLUB": - self.cleaned_data['target_id'] = self.cleaned_data['club'].id - elif self.cleaned_data['target_type'] == "COMPANY": - self.cleaned_data['target_id'] = self.cleaned_data['company'].id + if self.cleaned_data["target_type"] == "USER": + self.cleaned_data["target_id"] = self.cleaned_data["user"].id + elif self.cleaned_data["target_type"] == "ACCOUNT": + self.cleaned_data["target_id"] = self.cleaned_data[ + "club_account" + ].id + elif self.cleaned_data["target_type"] == "CLUB": + self.cleaned_data["target_id"] = self.cleaned_data["club"].id + elif self.cleaned_data["target_type"] == "COMPANY": + self.cleaned_data["target_id"] = self.cleaned_data["company"].id if self.cleaned_data.get("amount") is None: - self.add_error('amount', ValidationError(_("The amount must be set."))) + self.add_error("amount", ValidationError(_("The amount must be set."))) return self.cleaned_data def save(self): ret = super(OperationForm, self).save() - if self.instance.target_type == "ACCOUNT" and not self.instance.linked_operation and self.instance.target.has_open_journal() and self.cleaned_data['need_link']: + if ( + self.instance.target_type == "ACCOUNT" + and not self.instance.linked_operation + and self.instance.target.has_open_journal() + and self.cleaned_data["need_link"] + ): inst = self.instance club_account = inst.target - acc_type = AccountingType.objects.exclude(movement_type="NEUTRAL").exclude( - movement_type=inst.accounting_type.movement_type).order_by('code').first() # Select a random opposite accounting type + acc_type = ( + AccountingType.objects.exclude(movement_type="NEUTRAL") + .exclude(movement_type=inst.accounting_type.movement_type) + .order_by("code") + .first() + ) # Select a random opposite accounting type op = Operation( journal=club_account.get_open_journal(), amount=inst.amount, @@ -373,26 +479,27 @@ class OperationCreateView(CanCreateMixin, CreateView): """ Create an operation """ + model = Operation form_class = OperationForm - template_name = 'accounting/operation_edit.jinja' + template_name = "accounting/operation_edit.jinja" def get_form(self, form_class=None): - self.journal = GeneralJournal.objects.filter(id=self.kwargs['j_id']).first() + self.journal = GeneralJournal.objects.filter(id=self.kwargs["j_id"]).first() ca = self.journal.club_account if self.journal else None return self.form_class(club_account=ca, **self.get_form_kwargs()) def get_initial(self): ret = super(OperationCreateView, self).get_initial() if self.journal is not None: - ret['journal'] = self.journal.id + ret["journal"] = self.journal.id return ret def get_context_data(self, **kwargs): """ Add journal to the context """ kwargs = super(OperationCreateView, self).get_context_data(**kwargs) if self.journal: - kwargs['object'] = self.journal + kwargs["object"] = self.journal return kwargs @@ -400,15 +507,16 @@ class OperationEditView(CanEditMixin, UpdateView): """ An edit view, working as detail for the moment """ + model = Operation pk_url_kwarg = "op_id" form_class = OperationForm - template_name = 'accounting/operation_edit.jinja' + template_name = "accounting/operation_edit.jinja" def get_context_data(self, **kwargs): """ Add journal to the context """ kwargs = super(OperationEditView, self).get_context_data(**kwargs) - kwargs['object'] = self.object.journal + kwargs["object"] = self.object.journal return kwargs @@ -430,7 +538,7 @@ class OperationPDFView(CanViewMixin, DetailView): from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase import pdfmetrics - pdfmetrics.registerFont(TTFont('DejaVu', 'DejaVuSerif.ttf')) + pdfmetrics.registerFont(TTFont("DejaVu", "DejaVuSerif.ttf")) self.object = self.get_object() amount = self.object.amount @@ -450,11 +558,15 @@ class OperationPDFView(CanViewMixin, DetailView): else: target = self.object.target.get_display_name() - response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = 'filename="op-%d(%s_on_%s).pdf"' % (num, ti, club_name) + response = HttpResponse(content_type="application/pdf") + response["Content-Disposition"] = 'filename="op-%d(%s_on_%s).pdf"' % ( + num, + ti, + club_name, + ) p = canvas.Canvas(response) - p.setFont('DejaVu', 12) + p.setFont("DejaVu", 12) p.setTitle("%s %d" % (_("Operation"), num)) width, height = letter @@ -466,20 +578,29 @@ class OperationPDFView(CanViewMixin, DetailView): label = Table(labelStr, colWidths=[150], rowHeights=[20]) - label.setStyle(TableStyle([ - ('ALIGN', (0, 0), (-1, -1), 'RIGHT'), - ])) + label.setStyle(TableStyle([("ALIGN", (0, 0), (-1, -1), "RIGHT")])) w, h = label.wrapOn(label, 0, 0) label.drawOn(p, width - 180, height) - p.drawString(90, height - 100, _("Financial proof: ") + "OP%010d" % (id_op)) # Justificatif du libellé - p.drawString(90, height - 130, _("Club: %(club_name)s") % ({"club_name": club_name})) - p.drawString(90, height - 160, _("Label: %(op_label)s") % {"op_label": op_label if op_label is not None else ""}) + p.drawString( + 90, height - 100, _("Financial proof: ") + "OP%010d" % (id_op) + ) # Justificatif du libellé + p.drawString( + 90, height - 130, _("Club: %(club_name)s") % ({"club_name": club_name}) + ) + p.drawString( + 90, + height - 160, + _("Label: %(op_label)s") + % {"op_label": op_label if op_label is not None else ""}, + ) p.drawString(90, height - 190, _("Date: %(date)s") % {"date": date}) data = [] - data += [["%s" % (_("Credit").upper() if nature == 'CREDIT' else _("Debit").upper())]] + data += [ + ["%s" % (_("Credit").upper() if nature == "CREDIT" else _("Debit").upper())] + ] data += [[_("Amount: %(amount).2f €") % {"amount": amount}]] @@ -493,33 +614,50 @@ class OperationPDFView(CanViewMixin, DetailView): data += [[payment_mode]] - data += [["%s : %s" % (_("Debtor") if nature == 'CREDIT' else _("Creditor"), target), ""]] + data += [ + [ + "%s : %s" + % (_("Debtor") if nature == "CREDIT" else _("Creditor"), target), + "", + ] + ] data += [["%s \n%s" % (_("Comment:"), remark)]] - t = Table(data, colWidths=[(width - 90 * 2) / 2] * 2, rowHeights=[20, 20, 70, 20, 80]) - t.setStyle(TableStyle([ - ('ALIGN', (0, 0), (-1, -1), 'CENTER'), - ('VALIGN', (-2, -1), (-1, -1), 'TOP'), - ('VALIGN', (0, 0), (-1, -2), 'MIDDLE'), - ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.black), - ('SPAN', (0, 0), (1, 0)), # line DEBIT/CREDIT - ('SPAN', (0, 1), (1, 1)), # line amount - ('SPAN', (-2, -1), (-1, -1)), # line comment - ('SPAN', (0, -2), (-1, -2)), # line creditor/debtor - ('SPAN', (0, 2), (1, 2)), # line payment_mode - ('ALIGN', (0, 2), (1, 2), 'LEFT'), # line payment_mode - ('ALIGN', (-2, -1), (-1, -1), 'LEFT'), - ('BOX', (0, 0), (-1, -1), 0.25, colors.black), - ])) + t = Table( + data, colWidths=[(width - 90 * 2) / 2] * 2, rowHeights=[20, 20, 70, 20, 80] + ) + t.setStyle( + TableStyle( + [ + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("VALIGN", (-2, -1), (-1, -1), "TOP"), + ("VALIGN", (0, 0), (-1, -2), "MIDDLE"), + ("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black), + ("SPAN", (0, 0), (1, 0)), # line DEBIT/CREDIT + ("SPAN", (0, 1), (1, 1)), # line amount + ("SPAN", (-2, -1), (-1, -1)), # line comment + ("SPAN", (0, -2), (-1, -2)), # line creditor/debtor + ("SPAN", (0, 2), (1, 2)), # line payment_mode + ("ALIGN", (0, 2), (1, 2), "LEFT"), # line payment_mode + ("ALIGN", (-2, -1), (-1, -1), "LEFT"), + ("BOX", (0, 0), (-1, -1), 0.25, colors.black), + ] + ) + ) signature = [] signature += [[_("Signature:")]] tSig = Table(signature, colWidths=[(width - 90 * 2)], rowHeights=[80]) - tSig.setStyle(TableStyle([ - ('VALIGN', (0, 0), (-1, -1), 'TOP'), - ('BOX', (0, 0), (-1, -1), 0.25, colors.black)])) + tSig.setStyle( + TableStyle( + [ + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("BOX", (0, 0), (-1, -1), 0.25, colors.black), + ] + ) + ) w, h = tSig.wrapOn(p, 0, 0) tSig.drawOn(p, 90, 200) @@ -540,18 +678,22 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): """ Display a statement sorted by labels """ + model = GeneralJournal pk_url_kwarg = "j_id" - template_name = 'accounting/journal_statement_nature.jinja' - current_tab = 'nature_statement' + template_name = "accounting/journal_statement_nature.jinja" + current_tab = "nature_statement" def statement(self, queryset, movement_type): ret = collections.OrderedDict() statement = collections.OrderedDict() total_sum = 0 - for sat in [None] + list(SimplifiedAccountingType.objects.order_by('label').all()): - sum = queryset.filter(accounting_type__movement_type=movement_type, - simpleaccounting_type=sat).aggregate(amount_sum=Sum('amount'))['amount_sum'] + for sat in [None] + list( + SimplifiedAccountingType.objects.order_by("label").all() + ): + sum = queryset.filter( + accounting_type__movement_type=movement_type, simpleaccounting_type=sat + ).aggregate(amount_sum=Sum("amount"))["amount_sum"] if sat: sat = sat.label else: @@ -564,7 +706,9 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): return ret def big_statement(self): - label_list = self.object.operations.order_by('label').values_list('label').distinct() + label_list = ( + self.object.operations.order_by("label").values_list("label").distinct() + ) labels = Label.objects.filter(id__in=label_list).all() statement = collections.OrderedDict() gen_statement = collections.OrderedDict() @@ -572,20 +716,28 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): gen_statement.update(self.statement(self.object.operations.all(), "CREDIT")) gen_statement.update(self.statement(self.object.operations.all(), "DEBIT")) statement[_("General statement")] = gen_statement - no_label_statement.update(self.statement(self.object.operations.filter(label=None).all(), "CREDIT")) - no_label_statement.update(self.statement(self.object.operations.filter(label=None).all(), "DEBIT")) + no_label_statement.update( + self.statement(self.object.operations.filter(label=None).all(), "CREDIT") + ) + no_label_statement.update( + self.statement(self.object.operations.filter(label=None).all(), "DEBIT") + ) statement[_("No label operations")] = no_label_statement for l in labels: l_stmt = collections.OrderedDict() - l_stmt.update(self.statement(self.object.operations.filter(label=l).all(), "CREDIT")) - l_stmt.update(self.statement(self.object.operations.filter(label=l).all(), "DEBIT")) + l_stmt.update( + self.statement(self.object.operations.filter(label=l).all(), "CREDIT") + ) + l_stmt.update( + self.statement(self.object.operations.filter(label=l).all(), "DEBIT") + ) statement[l] = l_stmt return statement def get_context_data(self, **kwargs): """ Add infos to the context """ kwargs = super(JournalNatureStatementView, self).get_context_data(**kwargs) - kwargs['statement'] = self.big_statement() + kwargs["statement"] = self.big_statement() return kwargs @@ -593,20 +745,29 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView): """ Calculate a dictionary with operation target and sum of operations """ + model = GeneralJournal pk_url_kwarg = "j_id" - template_name = 'accounting/journal_statement_person.jinja' - current_tab = 'person_statement' + template_name = "accounting/journal_statement_person.jinja" + current_tab = "person_statement" def sum_by_target(self, target_id, target_type, movement_type): - return self.object.operations.filter(accounting_type__movement_type=movement_type, - target_id=target_id, target_type=target_type).aggregate(amount_sum=Sum('amount'))['amount_sum'] + return self.object.operations.filter( + accounting_type__movement_type=movement_type, + target_id=target_id, + target_type=target_type, + ).aggregate(amount_sum=Sum("amount"))["amount_sum"] def statement(self, movement_type): statement = collections.OrderedDict() - for op in self.object.operations.filter(accounting_type__movement_type=movement_type).order_by('target_type', - 'target_id').distinct(): - statement[op.target] = self.sum_by_target(op.target_id, op.target_type, movement_type) + for op in ( + self.object.operations.filter(accounting_type__movement_type=movement_type) + .order_by("target_type", "target_id") + .distinct() + ): + statement[op.target] = self.sum_by_target( + op.target_id, op.target_type, movement_type + ) return statement def total(self, movement_type): @@ -615,10 +776,10 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): """ Add journal to the context """ kwargs = super(JournalPersonStatementView, self).get_context_data(**kwargs) - kwargs['credit_statement'] = self.statement("CREDIT") - kwargs['debit_statement'] = self.statement("DEBIT") - kwargs['total_credit'] = self.total("CREDIT") - kwargs['total_debit'] = self.total("DEBIT") + kwargs["credit_statement"] = self.statement("CREDIT") + kwargs["debit_statement"] = self.statement("DEBIT") + kwargs["total_credit"] = self.total("CREDIT") + kwargs["total_debit"] = self.total("DEBIT") return kwargs @@ -626,16 +787,18 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView) """ Calculate a dictionary with operation type and sum of operations """ + model = GeneralJournal pk_url_kwarg = "j_id" - template_name = 'accounting/journal_statement_accounting.jinja' + template_name = "accounting/journal_statement_accounting.jinja" current_tab = "accounting_statement" def statement(self): statement = collections.OrderedDict() - for at in AccountingType.objects.order_by('code').all(): + for at in AccountingType.objects.order_by("code").all(): sum_by_type = self.object.operations.filter( - accounting_type__code__startswith=at.code).aggregate(amount_sum=Sum('amount'))['amount_sum'] + accounting_type__code__startswith=at.code + ).aggregate(amount_sum=Sum("amount"))["amount_sum"] if sum_by_type: statement[at] = sum_by_type return statement @@ -643,86 +806,95 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView) def get_context_data(self, **kwargs): """ Add journal to the context """ kwargs = super(JournalAccountingStatementView, self).get_context_data(**kwargs) - kwargs['statement'] = self.statement() + kwargs["statement"] = self.statement() return kwargs + # Company views class CompanyListView(CanViewMixin, ListView): model = Company - template_name = 'accounting/co_list.jinja' + template_name = "accounting/co_list.jinja" class CompanyCreateView(CanCreateMixin, CreateView): """ Create a company """ + model = Company - fields = ['name'] - template_name = 'core/create.jinja' - success_url = reverse_lazy('accounting:co_list') + fields = ["name"] + template_name = "core/create.jinja" + success_url = reverse_lazy("accounting:co_list") class CompanyEditView(CanCreateMixin, UpdateView): """ Edit a company """ + model = Company pk_url_kwarg = "co_id" - fields = ['name'] - template_name = 'core/edit.jinja' - success_url = reverse_lazy('accounting:co_list') + fields = ["name"] + template_name = "core/edit.jinja" + success_url = reverse_lazy("accounting:co_list") # Label views + class LabelListView(CanViewMixin, DetailView): model = ClubAccount pk_url_kwarg = "clubaccount_id" - template_name = 'accounting/label_list.jinja' + template_name = "accounting/label_list.jinja" -class LabelCreateView(CanCreateMixin, CreateView): # FIXME we need to check the rights before creating the object +class LabelCreateView( + CanCreateMixin, CreateView +): # FIXME we need to check the rights before creating the object model = Label - form_class = modelform_factory(Label, fields=['name', 'club_account'], widgets={ - 'club_account': HiddenInput, - }) - template_name = 'core/create.jinja' + form_class = modelform_factory( + Label, fields=["name", "club_account"], widgets={"club_account": HiddenInput} + ) + template_name = "core/create.jinja" def get_initial(self): ret = super(LabelCreateView, self).get_initial() - if 'parent' in self.request.GET.keys(): - obj = ClubAccount.objects.filter(id=int(self.request.GET['parent'])).first() + if "parent" in self.request.GET.keys(): + obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first() if obj is not None: - ret['club_account'] = obj.id + ret["club_account"] = obj.id return ret class LabelEditView(CanEditMixin, UpdateView): model = Label pk_url_kwarg = "label_id" - fields = ['name'] - template_name = 'core/edit.jinja' + fields = ["name"] + template_name = "core/edit.jinja" class LabelDeleteView(CanEditMixin, DeleteView): model = Label pk_url_kwarg = "label_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def get_success_url(self): return self.object.get_absolute_url() class CloseCustomerAccountForm(forms.Form): - user = AutoCompleteSelectField('users', label=_('Refound this account'), help_text=None, required=True) + user = AutoCompleteSelectField( + "users", label=_("Refound this account"), help_text=None, required=True + ) class RefoundAccountView(FormView): """ Create a selling with the same amount than the current user money """ + template_name = "accounting/refound_account.jinja" form_class = CloseCustomerAccountForm @@ -743,21 +915,28 @@ class RefoundAccountView(FormView): return super(RefoundAccountView, self).post(self, request, *arg, **kwargs) def form_valid(self, form): - self.customer = form.cleaned_data['user'] + self.customer = form.cleaned_data["user"] self.create_selling() return super(RefoundAccountView, self).form_valid(form) def get_success_url(self): - return reverse('accounting:refound_account') + return reverse("accounting:refound_account") def create_selling(self): with transaction.atomic(): uprice = self.customer.customer.amount - refound_club_counter = Counter.objects.get(id=settings.SITH_COUNTER_REFOUND_ID) + refound_club_counter = Counter.objects.get( + id=settings.SITH_COUNTER_REFOUND_ID + ) refound_club = refound_club_counter.club - s = Selling(label=_('Refound account'), unit_price=uprice, - quantity=1, seller=self.operator, - customer=self.customer.customer, - club=refound_club, counter=refound_club_counter, - product=Product.objects.get(id=settings.SITH_PRODUCT_REFOUND_ID)) + s = Selling( + label=_("Refound account"), + unit_price=uprice, + quantity=1, + seller=self.operator, + customer=self.customer.customer, + club=refound_club, + counter=refound_club_counter, + product=Product.objects.get(id=settings.SITH_PRODUCT_REFOUND_ID), + ) s.save() diff --git a/api/__init__.py b/api/__init__.py index 0a9419f8..0ace29c4 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/api/urls.py b/api/urls.py index 1289668f..60a4922c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -29,25 +29,28 @@ from rest_framework import routers # Router config router = routers.DefaultRouter() -router.register(r'counter', CounterViewSet, base_name='api_counter') -router.register(r'user', UserViewSet, base_name='api_user') -router.register(r'club', ClubViewSet, base_name='api_club') -router.register(r'group', GroupViewSet, base_name='api_group') +router.register(r"counter", CounterViewSet, base_name="api_counter") +router.register(r"user", UserViewSet, base_name="api_user") +router.register(r"club", ClubViewSet, base_name="api_club") +router.register(r"group", GroupViewSet, base_name="api_group") # Launderette -router.register(r'launderette/place', LaunderettePlaceViewSet, - base_name='api_launderette_place') -router.register(r'launderette/machine', LaunderetteMachineViewSet, - base_name='api_launderette_machine') -router.register(r'launderette/token', LaunderetteTokenViewSet, - base_name='api_launderette_token') +router.register( + r"launderette/place", LaunderettePlaceViewSet, base_name="api_launderette_place" +) +router.register( + r"launderette/machine", + LaunderetteMachineViewSet, + base_name="api_launderette_machine", +) +router.register( + r"launderette/token", LaunderetteTokenViewSet, base_name="api_launderette_token" +) urlpatterns = [ - # API - url(r'^', include(router.urls)), - url(r'^login/', include('rest_framework.urls', namespace='rest_framework')), - url(r'^markdown$', RenderMarkdown, name='api_markdown'), - url(r'^mailings$', FetchMailingLists, name='mailings_fetch') - + url(r"^", include(router.urls)), + url(r"^login/", include("rest_framework.urls", namespace="rest_framework")), + url(r"^markdown$", RenderMarkdown, name="api_markdown"), + url(r"^mailings$", FetchMailingLists, name="mailings_fetch"), ] diff --git a/api/views/__init__.py b/api/views/__init__.py index a0676bbe..90f3fc0e 100644 --- a/api/views/__init__.py +++ b/api/views/__init__.py @@ -30,19 +30,21 @@ from django.db.models.query import QuerySet from core.views import can_view, can_edit + def check_if(obj, user, test): """ Detect if it's a single object or a queryset aply a given test on individual object and return global permission """ - if (isinstance(obj, QuerySet)): + if isinstance(obj, QuerySet): for o in obj: - if (test(o, user) is False): + if test(o, user) is False: return False return True else: return test(obj, user) + class ManageModelMixin: @detail_route() def id(self, request, pk=None): @@ -53,19 +55,19 @@ class ManageModelMixin: serializer = self.get_serializer(self.queryset) return Response(serializer.data) -class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet): +class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet): def dispatch(self, request, *arg, **kwargs): - res = super(RightModelViewSet, - self).dispatch(request, *arg, **kwargs) + res = super(RightModelViewSet, self).dispatch(request, *arg, **kwargs) obj = self.queryset user = self.request.user try: - if (request.method == 'GET' and check_if(obj, user, can_view)): + if request.method == "GET" and check_if(obj, user, can_view): return res - if (request.method != 'GET' and check_if(obj, user, can_edit)): + if request.method != "GET" and check_if(obj, user, can_edit): return res - except: pass # To prevent bug with Anonymous user + except: + pass # To prevent bug with Anonymous user raise PermissionDenied @@ -74,4 +76,4 @@ from .counter import * from .user import * from .club import * from .group import * -from .launderette import * \ No newline at end of file +from .launderette import * diff --git a/api/views/api.py b/api/views/api.py index a7d098e3..efd9ec42 100644 --- a/api/views/api.py +++ b/api/views/api.py @@ -30,15 +30,14 @@ from rest_framework.views import APIView from core.templatetags.renderer import markdown -@api_view(['POST']) +@api_view(["POST"]) @renderer_classes((StaticHTMLRenderer,)) def RenderMarkdown(request): """ Render Markdown """ try: - data = markdown(request.POST['text']) + data = markdown(request.POST["text"]) except: - data = 'Error' + data = "Error" return Response(data) - diff --git a/api/views/club.py b/api/views/club.py index e4d4b30a..38ce16be 100644 --- a/api/views/club.py +++ b/api/views/club.py @@ -36,10 +36,9 @@ from api.views import RightModelViewSet class ClubSerializer(serializers.ModelSerializer): - class Meta: model = Club - fields = ('id', 'name', 'unix_name', 'address', 'members') + fields = ("id", "name", "unix_name", "address", "members") class ClubViewSet(RightModelViewSet): @@ -51,13 +50,15 @@ class ClubViewSet(RightModelViewSet): queryset = Club.objects.all() -@api_view(['GET']) +@api_view(["GET"]) @renderer_classes((StaticHTMLRenderer,)) def FetchMailingLists(request): - key = request.GET.get('key', '') + key = request.GET.get("key", "") if key != settings.SITH_MAILING_FETCH_KEY: raise PermissionDenied - data = '' - for mailing in Mailing.objects.filter(is_moderated=True, club__is_active=True).all(): + data = "" + for mailing in Mailing.objects.filter( + is_moderated=True, club__is_active=True + ).all(): data += mailing.fetch_format() + "\n" return Response(data) diff --git a/api/views/counter.py b/api/views/counter.py index 2df78e73..43ac1885 100644 --- a/api/views/counter.py +++ b/api/views/counter.py @@ -35,14 +35,12 @@ class CounterSerializer(serializers.ModelSerializer): is_open = serializers.BooleanField(read_only=True) barman_list = serializers.ListField( - child=serializers.IntegerField(), - read_only=True + child=serializers.IntegerField(), read_only=True ) class Meta: model = Counter - fields = ('id', 'name', 'type', 'club', - 'products', 'is_open', 'barman_list') + fields = ("id", "name", "type", "club", "products", "is_open", "barman_list") class CounterViewSet(RightModelViewSet): diff --git a/api/views/group.py b/api/views/group.py index ec46555e..5e6436af 100644 --- a/api/views/group.py +++ b/api/views/group.py @@ -30,7 +30,6 @@ from api.views import RightModelViewSet class GroupSerializer(serializers.ModelSerializer): - class Meta: model = RealGroup diff --git a/api/views/launderette.py b/api/views/launderette.py index fb4db382..940f8ebd 100644 --- a/api/views/launderette.py +++ b/api/views/launderette.py @@ -30,34 +30,45 @@ from launderette.models import Launderette, Machine, Token from api.views import RightModelViewSet + class LaunderettePlaceSerializer(serializers.ModelSerializer): machine_list = serializers.ListField( - child=serializers.IntegerField(), - read_only=True - ) - token_list = serializers.ListField( - child=serializers.IntegerField(), - read_only=True + child=serializers.IntegerField(), read_only=True ) + token_list = serializers.ListField(child=serializers.IntegerField(), read_only=True) class Meta: model = Launderette - fields = ('id', 'name', 'counter', 'machine_list', - 'token_list', 'get_absolute_url') + fields = ( + "id", + "name", + "counter", + "machine_list", + "token_list", + "get_absolute_url", + ) + class LaunderetteMachineSerializer(serializers.ModelSerializer): - class Meta: model = Machine - fields = ('id', 'name', 'type', 'is_working', 'launderette') + fields = ("id", "name", "type", "is_working", "launderette") + class LaunderetteTokenSerializer(serializers.ModelSerializer): - class Meta: model = Token - fields = ('id', 'name', 'type', 'launderette', 'borrow_date', - 'user', 'is_avaliable') + fields = ( + "id", + "name", + "type", + "launderette", + "borrow_date", + "user", + "is_avaliable", + ) + class LaunderettePlaceViewSet(RightModelViewSet): """ @@ -67,6 +78,7 @@ class LaunderettePlaceViewSet(RightModelViewSet): serializer_class = LaunderettePlaceSerializer queryset = Launderette.objects.all() + class LaunderetteMachineViewSet(RightModelViewSet): """ Manage Washing Machines (api/v1/launderette/machine/) @@ -89,7 +101,7 @@ class LaunderetteTokenViewSet(RightModelViewSet): """ Return all washing tokens (api/v1/launderette/token/washing) """ - self.queryset = self.queryset.filter(type='WASHING') + self.queryset = self.queryset.filter(type="WASHING") serializer = self.get_serializer(self.queryset, many=True) return Response(serializer.data) @@ -98,7 +110,7 @@ class LaunderetteTokenViewSet(RightModelViewSet): """ Return all drying tokens (api/v1/launderette/token/drying) """ - self.queryset = self.queryset.filter(type='DRYING') + self.queryset = self.queryset.filter(type="DRYING") serializer = self.get_serializer(self.queryset, many=True) return Response(serializer.data) @@ -107,7 +119,9 @@ class LaunderetteTokenViewSet(RightModelViewSet): """ Return all avaliable tokens (api/v1/launderette/token/avaliable) """ - self.queryset = self.queryset.filter(borrow_date__isnull=True, user__isnull=True) + self.queryset = self.queryset.filter( + borrow_date__isnull=True, user__isnull=True + ) serializer = self.get_serializer(self.queryset, many=True) return Response(serializer.data) @@ -116,6 +130,8 @@ class LaunderetteTokenViewSet(RightModelViewSet): """ Return all unavaliable tokens (api/v1/launderette/token/unavaliable) """ - self.queryset = self.queryset.filter(borrow_date__isnull=False, user__isnull=False) + self.queryset = self.queryset.filter( + borrow_date__isnull=False, user__isnull=False + ) serializer = self.get_serializer(self.queryset, many=True) return Response(serializer.data) diff --git a/api/views/user.py b/api/views/user.py index cb4d6a24..4724c292 100644 --- a/api/views/user.py +++ b/api/views/user.py @@ -34,11 +34,18 @@ from api.views import RightModelViewSet class UserSerializer(serializers.ModelSerializer): - class Meta: model = User - fields = ('id', 'first_name', 'last_name', 'email', - 'date_of_birth', 'nick_name', 'is_active', 'date_joined') + fields = ( + "id", + "first_name", + "last_name", + "email", + "date_of_birth", + "nick_name", + "is_active", + "date_joined", + ) class UserViewSet(RightModelViewSet): diff --git a/club/__init__.py b/club/__init__.py index 0a9419f8..0ace29c4 100644 --- a/club/__init__.py +++ b/club/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/club/migrations/0001_initial.py b/club/migrations/0001_initial.py index d93ac4c8..0bc1ec7e 100644 --- a/club/migrations/0001_initial.py +++ b/club/migrations/0001_initial.py @@ -7,28 +7,92 @@ import django.core.validators class Migration(migrations.Migration): - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Club', + name="Club", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('unix_name', models.CharField(unique=True, max_length=30, error_messages={'unique': 'A club with that unix name already exists.'}, verbose_name='unix name', validators=[django.core.validators.RegexValidator('^[a-z0-9][a-z0-9._-]*[a-z0-9]$', 'Enter a valid unix name. This value may contain only letters, numbers ./-/_ characters.')])), - ('address', models.CharField(max_length=254, verbose_name='address')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "unix_name", + models.CharField( + unique=True, + max_length=30, + error_messages={ + "unique": "A club with that unix name already exists." + }, + verbose_name="unix name", + validators=[ + django.core.validators.RegexValidator( + "^[a-z0-9][a-z0-9._-]*[a-z0-9]$", + "Enter a valid unix name. This value may contain only letters, numbers ./-/_ characters.", + ) + ], + ), + ), + ("address", models.CharField(max_length=254, verbose_name="address")), ], ), migrations.CreateModel( - name='Membership', + name="Membership", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('start_date', models.DateField(verbose_name='start date', auto_now=True)), - ('end_date', models.DateField(null=True, verbose_name='end date', blank=True)), - ('role', models.IntegerField(choices=[(0, 'Curious'), (1, 'Active member'), (2, 'Board member'), (3, 'IT supervisor'), (4, 'Secretary'), (5, 'Communication supervisor'), (7, 'Treasurer'), (9, 'Vice-President'), (10, 'President')], default=0, verbose_name='role')), - ('description', models.CharField(max_length=128, blank=True, verbose_name='description')), - ('club', models.ForeignKey(verbose_name='club', to='club.Club', related_name='members')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "start_date", + models.DateField(verbose_name="start date", auto_now=True), + ), + ( + "end_date", + models.DateField(null=True, verbose_name="end date", blank=True), + ), + ( + "role", + models.IntegerField( + choices=[ + (0, "Curious"), + (1, "Active member"), + (2, "Board member"), + (3, "IT supervisor"), + (4, "Secretary"), + (5, "Communication supervisor"), + (7, "Treasurer"), + (9, "Vice-President"), + (10, "President"), + ], + default=0, + verbose_name="role", + ), + ), + ( + "description", + models.CharField( + max_length=128, blank=True, verbose_name="description" + ), + ), + ( + "club", + models.ForeignKey( + verbose_name="club", to="club.Club", related_name="members" + ), + ), ], ), ] diff --git a/club/migrations/0002_auto_20160824_2152.py b/club/migrations/0002_auto_20160824_2152.py index 8c8a0fc8..b6de6b01 100644 --- a/club/migrations/0002_auto_20160824_2152.py +++ b/club/migrations/0002_auto_20160824_2152.py @@ -9,39 +9,57 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('club', '0001_initial'), - ('core', '0001_initial'), + ("club", "0001_initial"), + ("core", "0001_initial"), ] operations = [ migrations.AddField( - model_name='membership', - name='user', - field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, related_name='membership'), + model_name="membership", + name="user", + field=models.ForeignKey( + verbose_name="user", + to=settings.AUTH_USER_MODEL, + related_name="membership", + ), ), migrations.AddField( - model_name='club', - name='edit_groups', - field=models.ManyToManyField(to='core.Group', blank=True, related_name='editable_club'), + model_name="club", + name="edit_groups", + field=models.ManyToManyField( + to="core.Group", blank=True, related_name="editable_club" + ), ), migrations.AddField( - model_name='club', - name='home', - field=models.OneToOneField(blank=True, null=True, related_name='home_of_club', verbose_name='home', to='core.SithFile'), + model_name="club", + name="home", + field=models.OneToOneField( + blank=True, + null=True, + related_name="home_of_club", + verbose_name="home", + to="core.SithFile", + ), ), migrations.AddField( - model_name='club', - name='owner_group', - field=models.ForeignKey(default=1, to='core.Group', related_name='owned_club'), + model_name="club", + name="owner_group", + field=models.ForeignKey( + default=1, to="core.Group", related_name="owned_club" + ), ), migrations.AddField( - model_name='club', - name='parent', - field=models.ForeignKey(null=True, to='club.Club', related_name='children', blank=True), + model_name="club", + name="parent", + field=models.ForeignKey( + null=True, to="club.Club", related_name="children", blank=True + ), ), migrations.AddField( - model_name='club', - name='view_groups', - field=models.ManyToManyField(to='core.Group', blank=True, related_name='viewable_club'), + model_name="club", + name="view_groups", + field=models.ManyToManyField( + to="core.Group", blank=True, related_name="viewable_club" + ), ), ] diff --git a/club/migrations/0003_auto_20160902_2042.py b/club/migrations/0003_auto_20160902_2042.py index f0d7e014..15eb34ad 100644 --- a/club/migrations/0003_auto_20160902_2042.py +++ b/club/migrations/0003_auto_20160902_2042.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('club', '0002_auto_20160824_2152'), - ] + dependencies = [("club", "0002_auto_20160824_2152")] operations = [ migrations.AlterField( - model_name='membership', - name='start_date', - field=models.DateField(verbose_name='start date'), - ), + model_name="membership", + name="start_date", + field=models.DateField(verbose_name="start date"), + ) ] diff --git a/club/migrations/0004_auto_20160915_1057.py b/club/migrations/0004_auto_20160915_1057.py index 05bd4958..6cdde0a6 100644 --- a/club/migrations/0004_auto_20160915_1057.py +++ b/club/migrations/0004_auto_20160915_1057.py @@ -7,14 +7,16 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('club', '0003_auto_20160902_2042'), - ] + dependencies = [("club", "0003_auto_20160902_2042")] operations = [ migrations.AlterField( - model_name='membership', - name='user', - field=models.ForeignKey(verbose_name='user', related_name='memberships', to=settings.AUTH_USER_MODEL), - ), + model_name="membership", + name="user", + field=models.ForeignKey( + verbose_name="user", + related_name="memberships", + to=settings.AUTH_USER_MODEL, + ), + ) ] diff --git a/club/migrations/0005_auto_20161120_1149.py b/club/migrations/0005_auto_20161120_1149.py index df8d8126..93193f50 100644 --- a/club/migrations/0005_auto_20161120_1149.py +++ b/club/migrations/0005_auto_20161120_1149.py @@ -7,14 +7,19 @@ import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('club', '0004_auto_20160915_1057'), - ] + dependencies = [("club", "0004_auto_20160915_1057")] operations = [ migrations.AlterField( - model_name='club', - name='home', - field=models.OneToOneField(related_name='home_of_club', blank=True, on_delete=django.db.models.deletion.SET_NULL, verbose_name='home', null=True, to='core.SithFile'), - ), + model_name="club", + name="home", + field=models.OneToOneField( + related_name="home_of_club", + blank=True, + on_delete=django.db.models.deletion.SET_NULL, + verbose_name="home", + null=True, + to="core.SithFile", + ), + ) ] diff --git a/club/migrations/0006_auto_20161229_0040.py b/club/migrations/0006_auto_20161229_0040.py index 58a0f676..6bb03661 100644 --- a/club/migrations/0006_auto_20161229_0040.py +++ b/club/migrations/0006_auto_20161229_0040.py @@ -7,14 +7,14 @@ import django.utils.timezone class Migration(migrations.Migration): - dependencies = [ - ('club', '0005_auto_20161120_1149'), - ] + dependencies = [("club", "0005_auto_20161120_1149")] operations = [ migrations.AlterField( - model_name='membership', - name='start_date', - field=models.DateField(verbose_name='start date', default=django.utils.timezone.now), - ), + model_name="membership", + name="start_date", + field=models.DateField( + verbose_name="start date", default=django.utils.timezone.now + ), + ) ] diff --git a/club/migrations/0007_auto_20170324_0917.py b/club/migrations/0007_auto_20170324_0917.py index dd215472..9faa9e75 100644 --- a/club/migrations/0007_auto_20170324_0917.py +++ b/club/migrations/0007_auto_20170324_0917.py @@ -6,13 +6,10 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('club', '0006_auto_20161229_0040'), - ] + dependencies = [("club", "0006_auto_20161229_0040")] operations = [ migrations.AlterModelOptions( - name='club', - options={'ordering': ['name', 'unix_name']}, - ), + name="club", options={"ordering": ["name", "unix_name"]} + ) ] diff --git a/club/migrations/0008_auto_20170515_2214.py b/club/migrations/0008_auto_20170515_2214.py index 48f5b3b6..6b65c629 100644 --- a/club/migrations/0008_auto_20170515_2214.py +++ b/club/migrations/0008_auto_20170515_2214.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('club', '0007_auto_20170324_0917'), - ] + dependencies = [("club", "0007_auto_20170324_0917")] operations = [ migrations.AlterField( - model_name='club', - name='id', + model_name="club", + name="id", field=models.AutoField(primary_key=True, serialize=False, db_index=True), - ), + ) ] diff --git a/club/migrations/0009_auto_20170822_2232.py b/club/migrations/0009_auto_20170822_2232.py index 67fc9cf6..a7d84c3a 100644 --- a/club/migrations/0009_auto_20170822_2232.py +++ b/club/migrations/0009_auto_20170822_2232.py @@ -11,31 +11,98 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('club', '0008_auto_20170515_2214'), + ("club", "0008_auto_20170515_2214"), ] operations = [ migrations.CreateModel( - name='Mailing', + name="Mailing", fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)), - ('email', models.CharField(max_length=256, unique=True, validators=[django.core.validators.RegexValidator(re.compile('(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*\\Z|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"\\Z)', 34), 'Enter a valid address. Only the root of the address is needed.')], verbose_name='Email address')), - ('is_moderated', models.BooleanField(default=False, verbose_name='is moderated')), - ('club', models.ForeignKey(verbose_name='Club', related_name='mailings', to='club.Club')), - ('moderator', models.ForeignKey(null=True, verbose_name='moderator', related_name='moderated_mailings', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + verbose_name="ID", + serialize=False, + primary_key=True, + ), + ), + ( + "email", + models.CharField( + max_length=256, + unique=True, + validators=[ + django.core.validators.RegexValidator( + re.compile( + "(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\\Z|^\"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*\"\\Z)", + 34, + ), + "Enter a valid address. Only the root of the address is needed.", + ) + ], + verbose_name="Email address", + ), + ), + ( + "is_moderated", + models.BooleanField(default=False, verbose_name="is moderated"), + ), + ( + "club", + models.ForeignKey( + verbose_name="Club", related_name="mailings", to="club.Club" + ), + ), + ( + "moderator", + models.ForeignKey( + null=True, + verbose_name="moderator", + related_name="moderated_mailings", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='MailingSubscription', + name="MailingSubscription", fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)), - ('email', models.EmailField(max_length=254, verbose_name='Email address')), - ('mailing', models.ForeignKey(verbose_name='Mailing', related_name='subscriptions', to='club.Mailing')), - ('user', models.ForeignKey(null=True, verbose_name='User', related_name='mailing_subscriptions', blank=True, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + verbose_name="ID", + serialize=False, + primary_key=True, + ), + ), + ( + "email", + models.EmailField(max_length=254, verbose_name="Email address"), + ), + ( + "mailing", + models.ForeignKey( + verbose_name="Mailing", + related_name="subscriptions", + to="club.Mailing", + ), + ), + ( + "user", + models.ForeignKey( + null=True, + verbose_name="User", + related_name="mailing_subscriptions", + blank=True, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.AlterUniqueTogether( - name='mailingsubscription', - unique_together=set([('user', 'email', 'mailing')]), + name="mailingsubscription", + unique_together=set([("user", "email", "mailing")]), ), ] diff --git a/club/migrations/0010_auto_20170912_2028.py b/club/migrations/0010_auto_20170912_2028.py index af3e2bdf..ff6f76ec 100644 --- a/club/migrations/0010_auto_20170912_2028.py +++ b/club/migrations/0010_auto_20170912_2028.py @@ -12,34 +12,44 @@ def generate_club_pages(apps, schema_editor): club.make_page() for child in Club.objects.filter(parent=club).all(): recursive_generate_club_page(child) + for club in Club.objects.filter(parent=None).all(): recursive_generate_club_page(club) class Migration(migrations.Migration): - dependencies = [ - ('core', '0024_auto_20170906_1317'), - ('club', '0010_club_logo'), - ] + dependencies = [("core", "0024_auto_20170906_1317"), ("club", "0010_club_logo")] operations = [ migrations.AddField( - model_name='club', - name='is_active', - field=models.BooleanField(default=True, verbose_name='is active'), + model_name="club", + name="is_active", + field=models.BooleanField(default=True, verbose_name="is active"), ), migrations.AddField( - model_name='club', - name='page', - field=models.OneToOneField(related_name='club', blank=True, null=True, to='core.Page'), + model_name="club", + name="page", + field=models.OneToOneField( + related_name="club", blank=True, null=True, to="core.Page" + ), ), migrations.AddField( - model_name='club', - name='short_description', - field=models.CharField(verbose_name='short description', max_length=1000, default='', blank=True, null=True), + model_name="club", + name="short_description", + field=models.CharField( + verbose_name="short description", + max_length=1000, + default="", + blank=True, + null=True, + ), + ), + PsqlRunOnly( + "SET CONSTRAINTS ALL IMMEDIATE", reverse_sql=migrations.RunSQL.noop ), - PsqlRunOnly('SET CONSTRAINTS ALL IMMEDIATE', reverse_sql=migrations.RunSQL.noop), migrations.RunPython(generate_club_pages), - PsqlRunOnly(migrations.RunSQL.noop, reverse_sql='SET CONSTRAINTS ALL IMMEDIATE'), + PsqlRunOnly( + migrations.RunSQL.noop, reverse_sql="SET CONSTRAINTS ALL IMMEDIATE" + ), ] diff --git a/club/migrations/0010_club_logo.py b/club/migrations/0010_club_logo.py index 9e8d4288..6dab697d 100644 --- a/club/migrations/0010_club_logo.py +++ b/club/migrations/0010_club_logo.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('club', '0009_auto_20170822_2232'), - ] + dependencies = [("club", "0009_auto_20170822_2232")] operations = [ migrations.AddField( - model_name='club', - name='logo', - field=models.ImageField(null=True, upload_to='club_logos', blank=True, verbose_name='logo'), - ), + model_name="club", + name="logo", + field=models.ImageField( + null=True, upload_to="club_logos", blank=True, verbose_name="logo" + ), + ) ] diff --git a/club/migrations/0011_auto_20180426_2013.py b/club/migrations/0011_auto_20180426_2013.py index 2c4cfa5e..8d63136e 100644 --- a/club/migrations/0011_auto_20180426_2013.py +++ b/club/migrations/0011_auto_20180426_2013.py @@ -7,14 +7,16 @@ import club.models class Migration(migrations.Migration): - dependencies = [ - ('club', '0010_auto_20170912_2028'), - ] + dependencies = [("club", "0010_auto_20170912_2028")] operations = [ migrations.AlterField( - model_name='club', - name='owner_group', - field=models.ForeignKey(default=club.models.Club.get_default_owner_group, related_name='owned_club', to='core.Group'), - ), + model_name="club", + name="owner_group", + field=models.ForeignKey( + default=club.models.Club.get_default_owner_group, + related_name="owned_club", + to="core.Group", + ), + ) ] diff --git a/club/models.py b/club/models.py index 575c06ad..00c9239d 100644 --- a/club/models.py +++ b/club/models.py @@ -43,40 +43,64 @@ class Club(models.Model): """ The Club class, made as a tree to allow nice tidy organization """ + id = models.AutoField(primary_key=True, db_index=True) - name = models.CharField(_('name'), max_length=64) - parent = models.ForeignKey('Club', related_name='children', null=True, blank=True) - unix_name = models.CharField(_('unix name'), max_length=30, unique=True, - validators=[ - validators.RegexValidator( - r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$', - _('Enter a valid unix name. This value may contain only ' - 'letters, numbers ./-/_ characters.') - ), - ], - error_messages={ - 'unique': _("A club with that unix name already exists."), - }, + name = models.CharField(_("name"), max_length=64) + parent = models.ForeignKey("Club", related_name="children", null=True, blank=True) + unix_name = models.CharField( + _("unix name"), + max_length=30, + unique=True, + validators=[ + validators.RegexValidator( + r"^[a-z0-9][a-z0-9._-]*[a-z0-9]$", + _( + "Enter a valid unix name. This value may contain only " + "letters, numbers ./-/_ characters." + ), + ) + ], + error_messages={"unique": _("A club with that unix name already exists.")}, ) - logo = models.ImageField(upload_to='club_logos', verbose_name=_('logo'), null=True, blank=True) - is_active = models.BooleanField(_('is active'), default=True) - short_description = models.CharField(_('short description'), max_length=1000, default='', blank=True, null=True) - address = models.CharField(_('address'), max_length=254) + logo = models.ImageField( + upload_to="club_logos", verbose_name=_("logo"), null=True, blank=True + ) + is_active = models.BooleanField(_("is active"), default=True) + short_description = models.CharField( + _("short description"), max_length=1000, default="", blank=True, null=True + ) + address = models.CharField(_("address"), max_length=254) # This function prevents generating migration upon settings change - def get_default_owner_group(): return settings.SITH_GROUP_ROOT_ID - owner_group = models.ForeignKey(Group, related_name="owned_club", default=get_default_owner_group) - edit_groups = models.ManyToManyField(Group, related_name="editable_club", blank=True) - view_groups = models.ManyToManyField(Group, related_name="viewable_club", blank=True) - home = models.OneToOneField(SithFile, related_name='home_of_club', verbose_name=_("home"), null=True, blank=True, - on_delete=models.SET_NULL) + def get_default_owner_group(): + return settings.SITH_GROUP_ROOT_ID + + owner_group = models.ForeignKey( + Group, related_name="owned_club", default=get_default_owner_group + ) + edit_groups = models.ManyToManyField( + Group, related_name="editable_club", blank=True + ) + view_groups = models.ManyToManyField( + Group, related_name="viewable_club", blank=True + ) + home = models.OneToOneField( + SithFile, + related_name="home_of_club", + verbose_name=_("home"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) page = models.OneToOneField(Page, related_name="club", blank=True, null=True) class Meta: - ordering = ['name', 'unix_name'] + ordering = ["name", "unix_name"] @cached_property def president(self): - return self.members.filter(role=settings.SITH_CLUB_ROLES_ID['President'], end_date=None).first() + return self.members.filter( + role=settings.SITH_CLUB_ROLES_ID["President"], end_date=None + ).first() def check_loop(self): """Raise a validation error when a loop is found within the parent list""" @@ -84,7 +108,7 @@ class Club(models.Model): cur = self while cur.parent is not None: if cur in objs: - raise ValidationError(_('You can not make loops in clubs')) + raise ValidationError(_("You can not make loops in clubs")) objs.append(cur) cur = cur.parent @@ -130,7 +154,12 @@ class Club(models.Model): self.page.unset_lock() self.page.name = self.unix_name self.page.save(force_lock=True) - elif self.page and self.parent and self.parent.page and self.page.parent != self.parent.page: + elif ( + self.page + and self.parent + and self.parent.page + and self.page.parent != self.parent.page + ): self.page.unset_lock() self.page.parent = self.parent.page self.page.save(force_lock=True) @@ -150,7 +179,9 @@ class Club(models.Model): board.save() member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX) member.save() - subscribers = Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first() + subscribers = Group.objects.filter( + name=settings.SITH_MAIN_MEMBERS_GROUP + ).first() self.make_home() self.home.edit_groups = [board] self.home.view_groups = [member, subscribers] @@ -161,7 +192,7 @@ class Club(models.Model): return self.name def get_absolute_url(self): - return reverse('club:club_view', kwargs={'club_id': self.id}) + return reverse("club:club_view", kwargs={"club_id": self.id}) def get_display_name(self): return self.name @@ -223,24 +254,48 @@ class Membership(models.Model): A User is currently member of all the Clubs where its Membership has an end_date set to null/None. Otherwise, it's a past membership kept because it can be very useful to see who was in which Club in the past. """ - user = models.ForeignKey(User, verbose_name=_('user'), related_name="memberships", null=False, blank=False) - club = models.ForeignKey(Club, verbose_name=_('club'), related_name="members", null=False, blank=False) - start_date = models.DateField(_('start date'), default=timezone.now) - end_date = models.DateField(_('end date'), null=True, blank=True) - role = models.IntegerField(_('role'), choices=sorted(settings.SITH_CLUB_ROLES.items()), - default=sorted(settings.SITH_CLUB_ROLES.items())[0][0]) - description = models.CharField(_('description'), max_length=128, null=False, blank=True) + + user = models.ForeignKey( + User, + verbose_name=_("user"), + related_name="memberships", + null=False, + blank=False, + ) + club = models.ForeignKey( + Club, verbose_name=_("club"), related_name="members", null=False, blank=False + ) + start_date = models.DateField(_("start date"), default=timezone.now) + end_date = models.DateField(_("end date"), null=True, blank=True) + role = models.IntegerField( + _("role"), + choices=sorted(settings.SITH_CLUB_ROLES.items()), + default=sorted(settings.SITH_CLUB_ROLES.items())[0][0], + ) + description = models.CharField( + _("description"), max_length=128, null=False, blank=True + ) def clean(self): sub = User.objects.filter(pk=self.user.pk).first() if sub is None or not sub.is_subscribed: - raise ValidationError(_('User must be subscriber to take part to a club')) - if Membership.objects.filter(user=self.user).filter(club=self.club).filter(end_date=None).exists(): - raise ValidationError(_('User is already member of that club')) + raise ValidationError(_("User must be subscriber to take part to a club")) + if ( + Membership.objects.filter(user=self.user) + .filter(club=self.club) + .filter(end_date=None) + .exists() + ): + raise ValidationError(_("User is already member of that club")) def __str__(self): - return self.club.name + ' - ' + self.user.username + ' - ' + str(settings.SITH_CLUB_ROLES[self.role]) + str( - " - " + str(_('past member')) if self.end_date is not None else "" + return ( + self.club.name + + " - " + + self.user.username + + " - " + + str(settings.SITH_CLUB_ROLES[self.role]) + + str(" - " + str(_("past member")) if self.end_date is not None else "") ) def is_owned_by(self, user): @@ -255,11 +310,13 @@ class Membership(models.Model): """ if user.memberships: ms = user.memberships.filter(club=self.club, end_date=None).first() - return (ms and ms.role >= self.role) or user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) + return (ms and ms.role >= self.role) or user.is_in_group( + settings.SITH_MAIN_BOARD_GROUP + ) return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) def get_absolute_url(self): - return reverse('club:club_members', kwargs={'club_id': self.club.id}) + return reverse("club:club_members", kwargs={"club_id": self.club.id}) class Mailing(models.Model): @@ -267,14 +324,27 @@ class Mailing(models.Model): This class correspond to a mailing list Remember that mailing lists should be validated by UTBM """ - club = models.ForeignKey(Club, verbose_name=_('Club'), related_name="mailings", null=False, blank=False) - email = models.CharField(_('Email address'), unique=True, null=False, blank=False, max_length=256, - validators=[ - RegexValidator(validate_email.user_regex, - _('Enter a valid address. Only the root of the address is needed.')) - ]) - is_moderated = models.BooleanField(_('is moderated'), default=False) - moderator = models.ForeignKey(User, related_name="moderated_mailings", verbose_name=_("moderator"), null=True) + + club = models.ForeignKey( + Club, verbose_name=_("Club"), related_name="mailings", null=False, blank=False + ) + email = models.CharField( + _("Email address"), + unique=True, + null=False, + blank=False, + max_length=256, + validators=[ + RegexValidator( + validate_email.user_regex, + _("Enter a valid address. Only the root of the address is needed."), + ) + ], + ) + is_moderated = models.BooleanField(_("is moderated"), default=False) + moderator = models.ForeignKey( + User, related_name="moderated_mailings", verbose_name=_("moderator"), null=True + ) def clean(self): if self.can_moderate(self.moderator): @@ -285,13 +355,17 @@ class Mailing(models.Model): @property def email_full(self): - return self.email + '@' + settings.SITH_MAILING_DOMAIN + return self.email + "@" + settings.SITH_MAILING_DOMAIN def can_moderate(self, user): return user.is_root or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) def is_owned_by(self, user): - return user.is_in_group(self) or user.is_root or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + return ( + user.is_in_group(self) + or user.is_root + or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + ) def can_view(self, user): return self.club.has_rights_in_club(user) @@ -305,16 +379,26 @@ class Mailing(models.Model): super(Mailing, self).delete() def fetch_format(self): - resp = self.email + ': ' + resp = self.email + ": " for sub in self.subscriptions.all(): resp += sub.fetch_format() return resp def save(self): if not self.is_moderated: - for user in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - if not user.notifications.filter(type="MAILING_MODERATION", viewed=False).exists(): - Notification(user=user, url=reverse('com:mailing_admin'), type="MAILING_MODERATION").save() + for user in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + if not user.notifications.filter( + type="MAILING_MODERATION", viewed=False + ).exists(): + Notification( + user=user, + url=reverse("com:mailing_admin"), + type="MAILING_MODERATION", + ).save() super(Mailing, self).save() def __str__(self): @@ -325,12 +409,25 @@ class MailingSubscription(models.Model): """ This class makes the link between user and mailing list """ - mailing = models.ForeignKey(Mailing, verbose_name=_('Mailing'), related_name="subscriptions", null=False, blank=False) - user = models.ForeignKey(User, verbose_name=_('User'), related_name="mailing_subscriptions", null=True, blank=True) - email = models.EmailField(_('Email address'), blank=False, null=False) + + mailing = models.ForeignKey( + Mailing, + verbose_name=_("Mailing"), + related_name="subscriptions", + null=False, + blank=False, + ) + user = models.ForeignKey( + User, + verbose_name=_("User"), + related_name="mailing_subscriptions", + null=True, + blank=True, + ) + email = models.EmailField(_("Email address"), blank=False, null=False) class Meta: - unique_together = (('user', 'email', 'mailing'),) + unique_together = (("user", "email", "mailing"),) def clean(self): if not self.user and not self.email: @@ -338,17 +435,25 @@ class MailingSubscription(models.Model): try: if self.user and not self.email: self.email = self.user.email - if MailingSubscription.objects.filter(mailing=self.mailing, email=self.email).exists(): - raise ValidationError(_("This email is already suscribed in this mailing")) + if MailingSubscription.objects.filter( + mailing=self.mailing, email=self.email + ).exists(): + raise ValidationError( + _("This email is already suscribed in this mailing") + ) except ObjectDoesNotExist: pass super(MailingSubscription, self).clean() def is_owned_by(self, user): - return self.mailing.club.has_rights_in_club(user) or user.is_root or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + return ( + self.mailing.club.has_rights_in_club(user) + or user.is_root + or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + ) def can_be_edited_by(self, user): - return (self.user is not None and user.id == self.user.id) + return self.user is not None and user.id == self.user.id @property def get_email(self): @@ -357,7 +462,7 @@ class MailingSubscription(models.Model): return self.email def fetch_format(self): - return self.get_email + ' ' + return self.get_email + " " def __str__(self): if self.user: diff --git a/club/tests.py b/club/tests.py index 87a2009f..c0409a9f 100644 --- a/club/tests.py +++ b/club/tests.py @@ -41,66 +41,92 @@ class ClubTest(TestCase): self.bdf = Club.objects.filter(unix_name="bdf").first() def test_create_add_user_to_club_from_root_ok(self): - self.client.login(username='root', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.skia.id, - "start_date": "12/06/2016", - "role": 3}) - response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) + self.client.login(username="root", password="plop") + self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.skia.id, "start_date": "12/06/2016", "role": 3}, + ) + response = self.client.get( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}) + ) self.assertTrue(response.status_code == 200) - self.assertTrue("S' Kia\\n Responsable info" in str(response.content)) + self.assertTrue( + "S' Kia\\n Responsable info" + in str(response.content) + ) def test_create_add_user_to_club_from_root_fail_not_subscriber(self): - self.client.login(username='root', password='plop') - response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.guy.id, - "start_date": "12/06/2016", - "role": 3}) + self.client.login(username="root", password="plop") + response = self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.guy.id, "start_date": "12/06/2016", "role": 3}, + ) self.assertTrue(response.status_code == 200) self.assertTrue('
  • ' in str(response.content)) - response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) - self.assertFalse("Guy Carlier\\n Responsable info" in str(response.content)) + response = self.client.get( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}) + ) + self.assertFalse( + "Guy Carlier\\n Responsable info" + in str(response.content) + ) def test_create_add_user_to_club_from_root_fail_already_in_club(self): - self.client.login(username='root', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.skia.id, - "start_date": "12/06/2016", - "role": 3}) - response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) - self.assertTrue("S' Kia\\n Responsable info" in str(response.content)) - response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.skia.id, - "start_date": "12/06/2016", - "role": 4}) + self.client.login(username="root", password="plop") + self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.skia.id, "start_date": "12/06/2016", "role": 3}, + ) + response = self.client.get( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}) + ) + self.assertTrue( + "S' Kia\\n Responsable info" + in str(response.content) + ) + response = self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.skia.id, "start_date": "12/06/2016", "role": 4}, + ) self.assertTrue(response.status_code == 200) - self.assertFalse("S' Kia\\n Secrétaire" in str(response.content)) + self.assertFalse( + "S' Kia\\n Secrétaire" + in str(response.content) + ) def test_create_add_user_to_club_from_skia_ok(self): - self.client.login(username='root', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.skia.id, - "start_date": "12/06/2016", - "role": 10}) - self.client.login(username='skia', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.rbatsbak.id, - "start_date": "12/06/2016", - "role": 9}) - response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) + self.client.login(username="root", password="plop") + self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.skia.id, "start_date": "12/06/2016", "role": 10}, + ) + self.client.login(username="skia", password="plop") + self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.rbatsbak.id, "start_date": "12/06/2016", "role": 9}, + ) + response = self.client.get( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}) + ) self.assertTrue(response.status_code == 200) - self.assertTrue("""Richard Batsbak\\n Vice-Pr\\xc3\\xa9sident""" in str(response.content)) + self.assertTrue( + """Richard Batsbak\\n Vice-Pr\\xc3\\xa9sident""" + in str(response.content) + ) def test_create_add_user_to_club_from_richard_fail(self): - self.client.login(username='root', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.rbatsbak.id, - "start_date": "12/06/2016", - "role": 3}) - self.client.login(username='rbatsbak', password='plop') - response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.skia.id, - "start_date": "12/06/2016", - "role": 10}) + self.client.login(username="root", password="plop") + self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.rbatsbak.id, "start_date": "12/06/2016", "role": 3}, + ) + self.client.login(username="rbatsbak", password="plop") + response = self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.skia.id, "start_date": "12/06/2016", "role": 10}, + ) self.assertTrue(response.status_code == 200) - self.assertTrue("
  • Vous n'avez pas la permission de faire cela
  • " in str(response.content)) + self.assertTrue( + "
  • Vous n'avez pas la permission de faire cela
  • " + in str(response.content) + ) diff --git a/club/urls.py b/club/urls.py index 4d41e120..8f446d65 100644 --- a/club/urls.py +++ b/club/urls.py @@ -28,30 +28,96 @@ from django.conf.urls import url from club.views import * urlpatterns = [ - url(r'^$', ClubListView.as_view(), name='club_list'), - url(r'^new$', ClubCreateView.as_view(), name='club_new'), - url(r'^stats$', ClubStatView.as_view(), name='club_stats'), - url(r'^(?P[0-9]+)/$', ClubView.as_view(), name='club_view'), - url(r'^(?P[0-9]+)/rev/(?P[0-9]+)/$', ClubRevView.as_view(), name='club_view_rev'), - url(r'^(?P[0-9]+)/hist$', ClubPageHistView.as_view(), name='club_hist'), - url(r'^(?P[0-9]+)/edit$', ClubEditView.as_view(), name='club_edit'), - url(r'^(?P[0-9]+)/edit/page$', ClubPageEditView.as_view(), name='club_edit_page'), - url(r'^(?P[0-9]+)/members$', ClubMembersView.as_view(), name='club_members'), - url(r'^(?P[0-9]+)/elderlies$', ClubOldMembersView.as_view(), name='club_old_members'), - url(r'^(?P[0-9]+)/sellings$', ClubSellingView.as_view(), name='club_sellings'), - url(r'^(?P[0-9]+)/sellings/csv$', ClubSellingCSVView.as_view(), name='sellings_csv'), - url(r'^(?P[0-9]+)/prop$', ClubEditPropView.as_view(), name='club_prop'), - url(r'^(?P[0-9]+)/tools$', ClubToolsView.as_view(), name='tools'), - url(r'^(?P[0-9]+)/mailing$', ClubMailingView.as_view(action="display"), name='mailing'), - url(r'^(?P[0-9]+)/mailing/new/mailing$', ClubMailingView.as_view(action="add_mailing"), name='mailing_create'), - url(r'^(?P[0-9]+)/mailing/new/subscription$', ClubMailingView.as_view(action="add_member"), name='mailing_subscription_create'), - url(r'^(?P[0-9]+)/mailing/generate$', MailingAutoGenerationView.as_view(), name='mailing_generate'), - url(r'^(?P[0-9]+)/mailing/clean$', MailingAutoCleanView.as_view(), name='mailing_clean'), - url(r'^(?P[0-9]+)/mailing/delete$', MailingDeleteView.as_view(), name='mailing_delete'), - url(r'^(?P[0-9]+)/mailing/delete/subscription$', MailingSubscriptionDeleteView.as_view(), name='mailing_subscription_delete'), - url(r'^membership/(?P[0-9]+)/set_old$', MembershipSetOldView.as_view(), name='membership_set_old'), - url(r'^(?P[0-9]+)/poster$', PosterListView.as_view(), name='poster_list'), - url(r'^(?P[0-9]+)/poster/create$', PosterCreateView.as_view(), name='poster_create'), - url(r'^(?P[0-9]+)/poster/(?P[0-9]+)/edit$', PosterEditView.as_view(), name='poster_edit'), - url(r'^(?P[0-9]+)/poster/(?P[0-9]+)/delete$', PosterDeleteView.as_view(), name='poster_delete'), + url(r"^$", ClubListView.as_view(), name="club_list"), + url(r"^new$", ClubCreateView.as_view(), name="club_new"), + url(r"^stats$", ClubStatView.as_view(), name="club_stats"), + url(r"^(?P[0-9]+)/$", ClubView.as_view(), name="club_view"), + url( + r"^(?P[0-9]+)/rev/(?P[0-9]+)/$", + ClubRevView.as_view(), + name="club_view_rev", + ), + url(r"^(?P[0-9]+)/hist$", ClubPageHistView.as_view(), name="club_hist"), + url(r"^(?P[0-9]+)/edit$", ClubEditView.as_view(), name="club_edit"), + url( + r"^(?P[0-9]+)/edit/page$", + ClubPageEditView.as_view(), + name="club_edit_page", + ), + url( + r"^(?P[0-9]+)/members$", ClubMembersView.as_view(), name="club_members" + ), + url( + r"^(?P[0-9]+)/elderlies$", + ClubOldMembersView.as_view(), + name="club_old_members", + ), + url( + r"^(?P[0-9]+)/sellings$", + ClubSellingView.as_view(), + name="club_sellings", + ), + url( + r"^(?P[0-9]+)/sellings/csv$", + ClubSellingCSVView.as_view(), + name="sellings_csv", + ), + url(r"^(?P[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop"), + url(r"^(?P[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"), + url( + r"^(?P[0-9]+)/mailing$", + ClubMailingView.as_view(action="display"), + name="mailing", + ), + url( + r"^(?P[0-9]+)/mailing/new/mailing$", + ClubMailingView.as_view(action="add_mailing"), + name="mailing_create", + ), + url( + r"^(?P[0-9]+)/mailing/new/subscription$", + ClubMailingView.as_view(action="add_member"), + name="mailing_subscription_create", + ), + url( + r"^(?P[0-9]+)/mailing/generate$", + MailingAutoGenerationView.as_view(), + name="mailing_generate", + ), + url( + r"^(?P[0-9]+)/mailing/clean$", + MailingAutoCleanView.as_view(), + name="mailing_clean", + ), + url( + r"^(?P[0-9]+)/mailing/delete$", + MailingDeleteView.as_view(), + name="mailing_delete", + ), + url( + r"^(?P[0-9]+)/mailing/delete/subscription$", + MailingSubscriptionDeleteView.as_view(), + name="mailing_subscription_delete", + ), + url( + r"^membership/(?P[0-9]+)/set_old$", + MembershipSetOldView.as_view(), + name="membership_set_old", + ), + url(r"^(?P[0-9]+)/poster$", PosterListView.as_view(), name="poster_list"), + url( + r"^(?P[0-9]+)/poster/create$", + PosterCreateView.as_view(), + name="poster_create", + ), + url( + r"^(?P[0-9]+)/poster/(?P[0-9]+)/edit$", + PosterEditView.as_view(), + name="poster_edit", + ), + url( + r"^(?P[0-9]+)/poster/(?P[0-9]+)/delete$", + PosterDeleteView.as_view(), + name="poster_delete", + ), ] diff --git a/com/__init__.py b/com/__init__.py index 0a9419f8..0ace29c4 100644 --- a/com/__init__.py +++ b/com/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/com/admin.py b/com/admin.py index df553be2..43ca4e1e 100644 --- a/com/admin.py +++ b/com/admin.py @@ -41,4 +41,3 @@ admin.site.register(News, NewsAdmin) admin.site.register(Weekmail, WeekmailAdmin) admin.site.register(Screen) admin.site.register(Poster) - diff --git a/com/migrations/0001_initial.py b/com/migrations/0001_initial.py index 0f4fe6fd..8edde739 100644 --- a/com/migrations/0001_initial.py +++ b/com/migrations/0001_initial.py @@ -6,17 +6,37 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Sith', + name="Sith", fields=[ - ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)), - ('alert_msg', models.TextField(default='', verbose_name='alert message', blank=True)), - ('info_msg', models.TextField(default='', verbose_name='info message', blank=True)), - ('index_page', models.TextField(default='', verbose_name='index page', blank=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + auto_created=True, + serialize=False, + primary_key=True, + ), + ), + ( + "alert_msg", + models.TextField( + default="", verbose_name="alert message", blank=True + ), + ), + ( + "info_msg", + models.TextField( + default="", verbose_name="info message", blank=True + ), + ), + ( + "index_page", + models.TextField(default="", verbose_name="index page", blank=True), + ), ], - ), + ) ] diff --git a/com/migrations/0002_news_newsdate.py b/com/migrations/0002_news_newsdate.py index 8dbac143..27241539 100644 --- a/com/migrations/0002_news_newsdate.py +++ b/com/migrations/0002_news_newsdate.py @@ -8,33 +8,100 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('club', '0005_auto_20161120_1149'), + ("club", "0005_auto_20161120_1149"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('com', '0001_initial'), + ("com", "0001_initial"), ] operations = [ migrations.CreateModel( - name='News', + name="News", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('title', models.CharField(max_length=64, verbose_name='title')), - ('summary', models.TextField(verbose_name='summary')), - ('content', models.TextField(verbose_name='content')), - ('type', models.CharField(choices=[('NOTICE', 'Notice'), ('EVENT', 'Event'), ('WEEKLY', 'Weekly'), ('CALL', 'Call')], default='EVENT', max_length=16, verbose_name='type')), - ('is_moderated', models.BooleanField(default=False, verbose_name='is moderated')), - ('author', models.ForeignKey(related_name='owned_news', to=settings.AUTH_USER_MODEL, verbose_name='author')), - ('club', models.ForeignKey(related_name='news', to='club.Club', verbose_name='club')), - ('moderator', models.ForeignKey(related_name='moderated_news', null=True, to=settings.AUTH_USER_MODEL, verbose_name='moderator')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + auto_created=True, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=64, verbose_name="title")), + ("summary", models.TextField(verbose_name="summary")), + ("content", models.TextField(verbose_name="content")), + ( + "type", + models.CharField( + choices=[ + ("NOTICE", "Notice"), + ("EVENT", "Event"), + ("WEEKLY", "Weekly"), + ("CALL", "Call"), + ], + default="EVENT", + max_length=16, + verbose_name="type", + ), + ), + ( + "is_moderated", + models.BooleanField(default=False, verbose_name="is moderated"), + ), + ( + "author", + models.ForeignKey( + related_name="owned_news", + to=settings.AUTH_USER_MODEL, + verbose_name="author", + ), + ), + ( + "club", + models.ForeignKey( + related_name="news", to="club.Club", verbose_name="club" + ), + ), + ( + "moderator", + models.ForeignKey( + related_name="moderated_news", + null=True, + to=settings.AUTH_USER_MODEL, + verbose_name="moderator", + ), + ), ], ), migrations.CreateModel( - name='NewsDate', + name="NewsDate", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('start_date', models.DateTimeField(null=True, blank=True, verbose_name='start_date')), - ('end_date', models.DateTimeField(null=True, blank=True, verbose_name='end_date')), - ('news', models.ForeignKey(related_name='dates', to='com.News', verbose_name='news_date')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + auto_created=True, + verbose_name="ID", + ), + ), + ( + "start_date", + models.DateTimeField( + null=True, blank=True, verbose_name="start_date" + ), + ), + ( + "end_date", + models.DateTimeField( + null=True, blank=True, verbose_name="end_date" + ), + ), + ( + "news", + models.ForeignKey( + related_name="dates", to="com.News", verbose_name="news_date" + ), + ), ], ), ] diff --git a/com/migrations/0003_auto_20170115_2300.py b/com/migrations/0003_auto_20170115_2300.py index 660c7635..d8fd9771 100644 --- a/com/migrations/0003_auto_20170115_2300.py +++ b/com/migrations/0003_auto_20170115_2300.py @@ -8,42 +8,81 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('club', '0006_auto_20161229_0040'), + ("club", "0006_auto_20161229_0040"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('com', '0002_news_newsdate'), + ("com", "0002_news_newsdate"), ] operations = [ migrations.CreateModel( - name='Weekmail', + name="Weekmail", fields=[ - ('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)), - ('title', models.CharField(max_length=64, verbose_name='title', blank=True)), - ('intro', models.TextField(verbose_name='intro', blank=True)), - ('joke', models.TextField(verbose_name='joke', blank=True)), - ('protip', models.TextField(verbose_name='protip', blank=True)), - ('conclusion', models.TextField(verbose_name='conclusion', blank=True)), - ('sent', models.BooleanField(verbose_name='sent', default=False)), + ( + "id", + models.AutoField( + serialize=False, + primary_key=True, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "title", + models.CharField(max_length=64, verbose_name="title", blank=True), + ), + ("intro", models.TextField(verbose_name="intro", blank=True)), + ("joke", models.TextField(verbose_name="joke", blank=True)), + ("protip", models.TextField(verbose_name="protip", blank=True)), + ("conclusion", models.TextField(verbose_name="conclusion", blank=True)), + ("sent", models.BooleanField(verbose_name="sent", default=False)), ], - options={ - 'ordering': ['-id'], - }, + options={"ordering": ["-id"]}, ), migrations.CreateModel( - name='WeekmailArticle', + name="WeekmailArticle", fields=[ - ('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)), - ('title', models.CharField(max_length=64, verbose_name='title')), - ('content', models.TextField(verbose_name='content')), - ('rank', models.IntegerField(verbose_name='rank', default=-1)), - ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='author', related_name='owned_weekmail_articles')), - ('club', models.ForeignKey(to='club.Club', verbose_name='club', related_name='weekmail_articles')), - ('weekmail', models.ForeignKey(to='com.Weekmail', verbose_name='weekmail', related_name='articles', null=True)), + ( + "id", + models.AutoField( + serialize=False, + primary_key=True, + verbose_name="ID", + auto_created=True, + ), + ), + ("title", models.CharField(max_length=64, verbose_name="title")), + ("content", models.TextField(verbose_name="content")), + ("rank", models.IntegerField(verbose_name="rank", default=-1)), + ( + "author", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, + verbose_name="author", + related_name="owned_weekmail_articles", + ), + ), + ( + "club", + models.ForeignKey( + to="club.Club", + verbose_name="club", + related_name="weekmail_articles", + ), + ), + ( + "weekmail", + models.ForeignKey( + to="com.Weekmail", + verbose_name="weekmail", + related_name="articles", + null=True, + ), + ), ], ), migrations.AddField( - model_name='sith', - name='weekmail_destinations', - field=models.TextField(verbose_name='weekmail destinations', default=''), + model_name="sith", + name="weekmail_destinations", + field=models.TextField(verbose_name="weekmail destinations", default=""), ), ] diff --git a/com/migrations/0004_auto_20171221_1614.py b/com/migrations/0004_auto_20171221_1614.py index e8d17887..1ebd9b86 100644 --- a/com/migrations/0004_auto_20171221_1614.py +++ b/com/migrations/0004_auto_20171221_1614.py @@ -9,36 +9,78 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('club', '0010_auto_20170912_2028'), + ("club", "0010_auto_20170912_2028"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('com', '0003_auto_20170115_2300'), + ("com", "0003_auto_20170115_2300"), ] operations = [ migrations.CreateModel( - name='Poster', + name="Poster", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(verbose_name='name', max_length=128, default='')), - ('file', models.ImageField(verbose_name='file', upload_to='com/posters')), - ('date_begin', models.DateTimeField(default=django.utils.timezone.now)), - ('date_end', models.DateTimeField(blank=True, null=True)), - ('display_time', models.IntegerField(verbose_name='display time', default=30)), - ('is_moderated', models.BooleanField(verbose_name='is moderated', default=False)), - ('club', models.ForeignKey(verbose_name='club', related_name='posters', to='club.Club')), - ('moderator', models.ForeignKey(verbose_name='moderator', blank=True, null=True, related_name='moderated_posters', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ( + "name", + models.CharField(verbose_name="name", max_length=128, default=""), + ), + ( + "file", + models.ImageField(verbose_name="file", upload_to="com/posters"), + ), + ("date_begin", models.DateTimeField(default=django.utils.timezone.now)), + ("date_end", models.DateTimeField(blank=True, null=True)), + ( + "display_time", + models.IntegerField(verbose_name="display time", default=30), + ), + ( + "is_moderated", + models.BooleanField(verbose_name="is moderated", default=False), + ), + ( + "club", + models.ForeignKey( + verbose_name="club", related_name="posters", to="club.Club" + ), + ), + ( + "moderator", + models.ForeignKey( + verbose_name="moderator", + blank=True, + null=True, + related_name="moderated_posters", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='Screen', + name="Screen", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(verbose_name='name', max_length=128)), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("name", models.CharField(verbose_name="name", max_length=128)), ], ), migrations.AddField( - model_name='poster', - name='screens', - field=models.ManyToManyField(related_name='posters', to='com.Screen'), + model_name="poster", + name="screens", + field=models.ManyToManyField(related_name="posters", to="com.Screen"), ), ] diff --git a/com/migrations/0005_auto_20180318_2227.py b/com/migrations/0005_auto_20180318_2227.py index 3b0cb351..9c6a3711 100644 --- a/com/migrations/0005_auto_20180318_2227.py +++ b/com/migrations/0005_auto_20180318_2227.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('com', '0004_auto_20171221_1614'), - ] + dependencies = [("com", "0004_auto_20171221_1614")] operations = [ migrations.AlterField( - model_name='poster', - name='display_time', - field=models.IntegerField(verbose_name='display time', default=15), - ), + model_name="poster", + name="display_time", + field=models.IntegerField(verbose_name="display time", default=15), + ) ] diff --git a/com/models.py b/com/models.py index 1b39563a..9f2065ca 100644 --- a/com/models.py +++ b/com/models.py @@ -40,10 +40,9 @@ from core.models import User, Preferences, RealGroup, Notification, SithFile from club.models import Club - - class Sith(models.Model): """A one instance class storing all the modifiable infos""" + alert_msg = models.TextField(_("alert message"), default="", blank=True) info_msg = models.TextField(_("info message"), default="", blank=True) index_page = models.TextField(_("index page"), default="", blank=True) @@ -57,23 +56,30 @@ class Sith(models.Model): NEWS_TYPES = [ - ('NOTICE', _('Notice')), - ('EVENT', _('Event')), - ('WEEKLY', _('Weekly')), - ('CALL', _('Call')), + ("NOTICE", _("Notice")), + ("EVENT", _("Event")), + ("WEEKLY", _("Weekly")), + ("CALL", _("Call")), ] class News(models.Model): """The news class""" + title = models.CharField(_("title"), max_length=64) summary = models.TextField(_("summary")) content = models.TextField(_("content")) - type = models.CharField(_("type"), max_length=16, choices=NEWS_TYPES, default="EVENT") + type = models.CharField( + _("type"), max_length=16, choices=NEWS_TYPES, default="EVENT" + ) club = models.ForeignKey(Club, related_name="news", verbose_name=_("club")) - author = models.ForeignKey(User, related_name="owned_news", verbose_name=_("author")) + author = models.ForeignKey( + User, related_name="owned_news", verbose_name=_("author") + ) is_moderated = models.BooleanField(_("is moderated"), default=False) - moderator = models.ForeignKey(User, related_name="moderated_news", verbose_name=_("moderator"), null=True) + moderator = models.ForeignKey( + User, related_name="moderated_news", verbose_name=_("moderator"), null=True + ) def is_owned_by(self, user): return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or user == self.author @@ -85,7 +91,7 @@ class News(models.Model): return self.is_moderated or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) def get_absolute_url(self): - return reverse('com:news_detail', kwargs={'news_id': self.id}) + return reverse("com:news_detail", kwargs={"news_id": self.id}) def get_full_url(self): return "https://%s%s" % (settings.SITH_URL, self.get_absolute_url()) @@ -95,15 +101,28 @@ class News(models.Model): def save(self, *args, **kwargs): super(News, self).save(*args, **kwargs) - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - Notification(user=u, url=reverse("com:news_admin_list"), - type="NEWS_MODERATION", param="1").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + Notification( + user=u, + url=reverse("com:news_admin_list"), + type="NEWS_MODERATION", + param="1", + ).save() + def news_notification_callback(notif): - count = News.objects.filter( - Q(dates__start_date__gt=timezone.now(), is_moderated=False) | - Q(type="NOTICE", is_moderated=False) - ).distinct().count() + count = ( + News.objects.filter( + Q(dates__start_date__gt=timezone.now(), is_moderated=False) + | Q(type="NOTICE", is_moderated=False) + ) + .distinct() + .count() + ) if count: notif.viewed = False notif.param = "%s" % count @@ -111,6 +130,7 @@ def news_notification_callback(notif): else: notif.viewed = True + class NewsDate(models.Model): """ A date class, useful for weekly events, or for events that just have no date @@ -118,9 +138,10 @@ class NewsDate(models.Model): This class allows more flexibilty managing the dates related to a news, particularly when this news is weekly, since we don't have to make copies """ + news = models.ForeignKey(News, related_name="dates", verbose_name=_("news_date")) - start_date = models.DateTimeField(_('start_date'), null=True, blank=True) - end_date = models.DateTimeField(_('end_date'), null=True, blank=True) + start_date = models.DateTimeField(_("start_date"), null=True, blank=True) + end_date = models.DateTimeField(_("end_date"), null=True, blank=True) def __str__(self): return "%s: %s - %s" % (self.news.title, self.start_date, self.end_date) @@ -130,6 +151,7 @@ class Weekmail(models.Model): """ The weekmail class """ + title = models.CharField(_("title"), max_length=64, blank=True) intro = models.TextField(_("intro"), blank=True) joke = models.TextField(_("joke"), blank=True) @@ -138,16 +160,21 @@ class Weekmail(models.Model): sent = models.BooleanField(_("sent"), default=False) class Meta: - ordering = ['-id'] + ordering = ["-id"] def send(self): - dest = [i[0] for i in Preferences.objects.filter(receive_weekmail=True).values_list('user__email')] + dest = [ + i[0] + for i in Preferences.objects.filter(receive_weekmail=True).values_list( + "user__email" + ) + ] with transaction.atomic(): email = EmailMultiAlternatives( subject=self.title, body=self.render_text(), from_email=settings.SITH_COM_EMAIL, - to=Sith.objects.first().weekmail_destinations.split(' '), + to=Sith.objects.first().weekmail_destinations.split(" "), bcc=dest, ) email.attach_alternative(self.render_html(), "text/html") @@ -157,14 +184,14 @@ class Weekmail(models.Model): Weekmail().save() def render_text(self): - return render(None, "com/weekmail_renderer_text.jinja", context={ - 'weekmail': self, - }).content.decode('utf-8') + return render( + None, "com/weekmail_renderer_text.jinja", context={"weekmail": self} + ).content.decode("utf-8") def render_html(self): - return render(None, "com/weekmail_renderer_html.jinja", context={ - 'weekmail': self, - }).content.decode('utf-8') + return render( + None, "com/weekmail_renderer_html.jinja", context={"weekmail": self} + ).content.decode("utf-8") def get_banner(self): return "http://" + settings.SITH_URL + static("com/img/weekmail_bannerA18.jpg") @@ -180,12 +207,18 @@ class Weekmail(models.Model): class WeekmailArticle(models.Model): - weekmail = models.ForeignKey(Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True) + weekmail = models.ForeignKey( + Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True + ) title = models.CharField(_("title"), max_length=64) content = models.TextField(_("content")) - author = models.ForeignKey(User, related_name="owned_weekmail_articles", verbose_name=_("author")) - club = models.ForeignKey(Club, related_name="weekmail_articles", verbose_name=_("club")) - rank = models.IntegerField(_('rank'), default=-1) + author = models.ForeignKey( + User, related_name="owned_weekmail_articles", verbose_name=_("author") + ) + club = models.ForeignKey( + Club, related_name="weekmail_articles", verbose_name=_("club") + ) + rank = models.IntegerField(_("rank"), default=-1) def is_owned_by(self, user): return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) @@ -199,7 +232,9 @@ class Screen(models.Model): def active_posters(self): now = timezone.now() - return self.posters.filter(is_moderated=True, date_begin__lte=now).filter(Q(date_end__isnull=True) | Q(date_end__gte=now)) + return self.posters.filter(is_moderated=True, date_begin__lte=now).filter( + Q(date_end__isnull=True) | Q(date_end__gte=now) + ) def is_owned_by(self, user): return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) @@ -209,21 +244,40 @@ class Screen(models.Model): class Poster(models.Model): - name = models.CharField(_("name"), blank=False, null=False, max_length=128, default="") + name = models.CharField( + _("name"), blank=False, null=False, max_length=128, default="" + ) file = models.ImageField(_("file"), null=False, upload_to="com/posters") - club = models.ForeignKey(Club, related_name="posters", verbose_name=_("club"), null=False) + club = models.ForeignKey( + Club, related_name="posters", verbose_name=_("club"), null=False + ) screens = models.ManyToManyField(Screen, related_name="posters") date_begin = models.DateTimeField(blank=False, null=False, default=timezone.now) date_end = models.DateTimeField(blank=True, null=True) - display_time = models.IntegerField(_("display time"), blank=False, null=False, default=15) + display_time = models.IntegerField( + _("display time"), blank=False, null=False, default=15 + ) is_moderated = models.BooleanField(_("is moderated"), default=False) - moderator = models.ForeignKey(User, related_name="moderated_posters", verbose_name=_("moderator"), null=True, blank=True) + moderator = models.ForeignKey( + User, + related_name="moderated_posters", + verbose_name=_("moderator"), + null=True, + blank=True, + ) def save(self, *args, **kwargs): if not self.is_moderated: - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - Notification(user=u, url=reverse("com:poster_moderate_list"), - type="POSTER_MODERATION").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + Notification( + user=u, + url=reverse("com:poster_moderate_list"), + type="POSTER_MODERATION", + ).save() return super(Poster, self).save(*args, **kwargs) def clean(self, *args, **kwargs): @@ -231,7 +285,9 @@ class Poster(models.Model): raise ValidationError(_("Begin date should be before end date")) def is_owned_by(self, user): - return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or Club.objects.filter(id__in=user.clubs_with_rights) + return user.is_in_group( + settings.SITH_GROUP_COM_ADMIN_ID + ) or Club.objects.filter(id__in=user.clubs_with_rights) def can_be_moderated_by(self, user): return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) diff --git a/com/tests.py b/com/tests.py index 4bcdcbdb..7272228a 100644 --- a/com/tests.py +++ b/com/tests.py @@ -34,25 +34,43 @@ class ComTest(TestCase): def setUp(self): call_command("populate") self.skia = User.objects.filter(username="skia").first() - self.com_group = RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first() + self.com_group = RealGroup.objects.filter( + id=settings.SITH_GROUP_COM_ADMIN_ID + ).first() self.skia.groups = [self.com_group] self.skia.save() - self.client.login(username=self.skia.username, password='plop') + self.client.login(username=self.skia.username, password="plop") def test_alert_msg(self): - response = self.client.post(reverse("com:alert_edit"), {"alert_msg": """ + response = self.client.post( + reverse("com:alert_edit"), + { + "alert_msg": """ ### ALERTE! **Caaaataaaapuuuulte!!!!** -"""}) +""" + }, + ) r = self.client.get(reverse("core:index")) self.assertTrue(r.status_code == 200) - self.assertTrue("""
    \\n

    ALERTE!

    \\n

    Caaaataaaapuuuulte!!!!

    """ in str(r.content)) + self.assertTrue( + """
    \\n

    ALERTE!

    \\n

    Caaaataaaapuuuulte!!!!

    """ + in str(r.content) + ) def test_info_msg(self): - response = self.client.post(reverse("com:info_edit"), {"info_msg": """ + response = self.client.post( + reverse("com:info_edit"), + { + "info_msg": """ ### INFO: **Caaaataaaapuuuulte!!!!** -"""}) +""" + }, + ) r = self.client.get(reverse("core:index")) self.assertTrue(r.status_code == 200) - self.assertTrue("""
    \\n

    INFO: Caaaataaaapuuuulte!!!!

    """ in str(r.content)) + self.assertTrue( + """
    \\n

    INFO: Caaaataaaapuuuulte!!!!

    """ + in str(r.content) + ) diff --git a/com/urls.py b/com/urls.py index 9f5b0d97..380ea8b1 100644 --- a/com/urls.py +++ b/com/urls.py @@ -28,35 +28,94 @@ from com.views import * from club.views import MailingDeleteView urlpatterns = [ - url(r'^sith/edit/alert$', AlertMsgEditView.as_view(), name='alert_edit'), - url(r'^sith/edit/info$', InfoMsgEditView.as_view(), name='info_edit'), - url(r'^sith/edit/index$', IndexEditView.as_view(), name='index_edit'), - url(r'^sith/edit/weekmail_destinations$', WeekmailDestinationEditView.as_view(), name='weekmail_destinations'), - url(r'^weekmail$', WeekmailEditView.as_view(), name='weekmail'), - url(r'^weekmail/preview$', WeekmailPreviewView.as_view(), name='weekmail_preview'), - url(r'^weekmail/new_article$', WeekmailArticleCreateView.as_view(), name='weekmail_article'), - url(r'^weekmail/article/(?P[0-9]+)/delete$', WeekmailArticleDeleteView.as_view(), name='weekmail_article_delete'), - url(r'^weekmail/article/(?P[0-9]+)/edit$', WeekmailArticleEditView.as_view(), name='weekmail_article_edit'), - url(r'^news$', NewsListView.as_view(), name='news_list'), - url(r'^news/admin$', NewsAdminListView.as_view(), name='news_admin_list'), - url(r'^news/create$', NewsCreateView.as_view(), name='news_new'), - url(r'^news/(?P[0-9]+)/delete$', NewsDeleteView.as_view(), name='news_delete'), - url(r'^news/(?P[0-9]+)/moderate$', NewsModerateView.as_view(), name='news_moderate'), - url(r'^news/(?P[0-9]+)/edit$', NewsEditView.as_view(), name='news_edit'), - url(r'^news/(?P[0-9]+)$', NewsDetailView.as_view(), name='news_detail'), - url(r'^mailings$', MailingListAdminView.as_view(), name='mailing_admin'), - url(r'^mailings/(?P[0-9]+)/moderate$', MailingModerateView.as_view(), name='mailing_moderate'), - url(r'^mailings/(?P[0-9]+)/delete$', MailingDeleteView.as_view(redirect_page='com:mailing_admin'), name='mailing_delete'), - url(r'^poster$', PosterListView.as_view(), name='poster_list'), - url(r'^poster/create$', PosterCreateView.as_view(), name='poster_create'), - url(r'^poster/(?P[0-9]+)/edit$', PosterEditView.as_view(), name='poster_edit'), - url(r'^poster/(?P[0-9]+)/delete$', PosterDeleteView.as_view(), name='poster_delete'), - url(r'^poster/moderate$', PosterModerateListView.as_view(), name='poster_moderate_list'), - url(r'^poster/(?P[0-9]+)/moderate$', PosterModerateView.as_view(), name='poster_moderate'), - url(r'^screen$', ScreenListView.as_view(), name='screen_list'), - url(r'^screen/create$', ScreenCreateView.as_view(), name='screen_create'), - url(r'^screen/(?P[0-9]+)/slideshow$', ScreenSlideshowView.as_view(), name='screen_slideshow'), - url(r'^screen/(?P[0-9]+)/edit$', ScreenEditView.as_view(), name='screen_edit'), - url(r'^screen/(?P[0-9]+)/delete$', ScreenDeleteView.as_view(), name='screen_delete'), + url(r"^sith/edit/alert$", AlertMsgEditView.as_view(), name="alert_edit"), + url(r"^sith/edit/info$", InfoMsgEditView.as_view(), name="info_edit"), + url(r"^sith/edit/index$", IndexEditView.as_view(), name="index_edit"), + url( + r"^sith/edit/weekmail_destinations$", + WeekmailDestinationEditView.as_view(), + name="weekmail_destinations", + ), + url(r"^weekmail$", WeekmailEditView.as_view(), name="weekmail"), + url(r"^weekmail/preview$", WeekmailPreviewView.as_view(), name="weekmail_preview"), + url( + r"^weekmail/new_article$", + WeekmailArticleCreateView.as_view(), + name="weekmail_article", + ), + url( + r"^weekmail/article/(?P[0-9]+)/delete$", + WeekmailArticleDeleteView.as_view(), + name="weekmail_article_delete", + ), + url( + r"^weekmail/article/(?P[0-9]+)/edit$", + WeekmailArticleEditView.as_view(), + name="weekmail_article_edit", + ), + url(r"^news$", NewsListView.as_view(), name="news_list"), + url(r"^news/admin$", NewsAdminListView.as_view(), name="news_admin_list"), + url(r"^news/create$", NewsCreateView.as_view(), name="news_new"), + url( + r"^news/(?P[0-9]+)/delete$", + NewsDeleteView.as_view(), + name="news_delete", + ), + url( + r"^news/(?P[0-9]+)/moderate$", + NewsModerateView.as_view(), + name="news_moderate", + ), + url(r"^news/(?P[0-9]+)/edit$", NewsEditView.as_view(), name="news_edit"), + url(r"^news/(?P[0-9]+)$", NewsDetailView.as_view(), name="news_detail"), + url(r"^mailings$", MailingListAdminView.as_view(), name="mailing_admin"), + url( + r"^mailings/(?P[0-9]+)/moderate$", + MailingModerateView.as_view(), + name="mailing_moderate", + ), + url( + r"^mailings/(?P[0-9]+)/delete$", + MailingDeleteView.as_view(redirect_page="com:mailing_admin"), + name="mailing_delete", + ), + url(r"^poster$", PosterListView.as_view(), name="poster_list"), + url(r"^poster/create$", PosterCreateView.as_view(), name="poster_create"), + url( + r"^poster/(?P[0-9]+)/edit$", + PosterEditView.as_view(), + name="poster_edit", + ), + url( + r"^poster/(?P[0-9]+)/delete$", + PosterDeleteView.as_view(), + name="poster_delete", + ), + url( + r"^poster/moderate$", + PosterModerateListView.as_view(), + name="poster_moderate_list", + ), + url( + r"^poster/(?P[0-9]+)/moderate$", + PosterModerateView.as_view(), + name="poster_moderate", + ), + url(r"^screen$", ScreenListView.as_view(), name="screen_list"), + url(r"^screen/create$", ScreenCreateView.as_view(), name="screen_create"), + url( + r"^screen/(?P[0-9]+)/slideshow$", + ScreenSlideshowView.as_view(), + name="screen_slideshow", + ), + url( + r"^screen/(?P[0-9]+)/edit$", + ScreenEditView.as_view(), + name="screen_edit", + ), + url( + r"^screen/(?P[0-9]+)/delete$", + ScreenDeleteView.as_view(), + name="screen_delete", + ), ] - diff --git a/com/views.py b/com/views.py index b4313167..c0fd10c0 100644 --- a/com/views.py +++ b/com/views.py @@ -41,7 +41,14 @@ from django import forms from datetime import timedelta from com.models import Sith, News, NewsDate, Weekmail, WeekmailArticle, Screen, Poster -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, CanCreateMixin, QuickNotifMixin +from core.views import ( + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + TabedViewMixin, + CanCreateMixin, + QuickNotifMixin, +) from core.views.forms import SelectDateTime from core.models import Notification, RealGroup, User from club.models import Club, Mailing @@ -55,23 +62,40 @@ sith = Sith.objects.first class PosterForm(forms.ModelForm): class Meta: model = Poster - fields = ['name', 'file', 'club', 'screens', 'date_begin', 'date_end', 'display_time'] - widgets = { - 'screens': forms.CheckboxSelectMultiple, - } + fields = [ + "name", + "file", + "club", + "screens", + "date_begin", + "date_end", + "display_time", + ] + widgets = {"screens": forms.CheckboxSelectMultiple} - date_begin = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), - widget=SelectDateTime, required=True, initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S")) - date_end = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), - widget=SelectDateTime, required=False) + date_begin = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Start date"), + widget=SelectDateTime, + required=True, + initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S"), + ) + date_end = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End date"), + widget=SelectDateTime, + required=False, + ) def __init__(self, *args, **kwargs): - self.user = kwargs.pop('user', None) + self.user = kwargs.pop("user", None) super(PosterForm, self).__init__(*args, **kwargs) if self.user: if not self.user.is_com_admin: - self.fields['club'].queryset = Club.objects.filter(id__in=self.user.clubs_with_rights) - self.fields.pop('display_time') + self.fields["club"].queryset = Club.objects.filter( + id__in=self.user.clubs_with_rights + ) + self.fields.pop("display_time") class ComTabsMixin(TabedViewMixin): @@ -80,51 +104,54 @@ class ComTabsMixin(TabedViewMixin): def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse('com:weekmail'), - 'slug': 'weekmail', - 'name': _("Weekmail"), - }) - tab_list.append({ - 'url': reverse('com:weekmail_destinations'), - 'slug': 'weekmail_destinations', - 'name': _("Weekmail destinations"), - }) - tab_list.append({ - 'url': reverse('com:index_edit'), - 'slug': 'index', - 'name': _("Index page"), - }) - tab_list.append({ - 'url': reverse('com:info_edit'), - 'slug': 'info', - 'name': _("Info message"), - }) - tab_list.append({ - 'url': reverse('com:alert_edit'), - 'slug': 'alert', - 'name': _("Alert message"), - }) - tab_list.append({ - 'url': reverse('com:mailing_admin'), - 'slug': 'mailings', - 'name': _("Mailing lists administration"), - }) - tab_list.append({ - 'url': reverse('com:poster_list'), - 'slug': 'posters', - 'name': _("Posters list"), - }) - tab_list.append({ - 'url': reverse('com:screen_list'), - 'slug': 'screens', - 'name': _("Screens list"), - }) + tab_list.append( + {"url": reverse("com:weekmail"), "slug": "weekmail", "name": _("Weekmail")} + ) + tab_list.append( + { + "url": reverse("com:weekmail_destinations"), + "slug": "weekmail_destinations", + "name": _("Weekmail destinations"), + } + ) + tab_list.append( + {"url": reverse("com:index_edit"), "slug": "index", "name": _("Index page")} + ) + tab_list.append( + {"url": reverse("com:info_edit"), "slug": "info", "name": _("Info message")} + ) + tab_list.append( + { + "url": reverse("com:alert_edit"), + "slug": "alert", + "name": _("Alert message"), + } + ) + tab_list.append( + { + "url": reverse("com:mailing_admin"), + "slug": "mailings", + "name": _("Mailing lists administration"), + } + ) + tab_list.append( + { + "url": reverse("com:poster_list"), + "slug": "posters", + "name": _("Posters list"), + } + ) + tab_list.append( + { + "url": reverse("com:screen_list"), + "slug": "screens", + "name": _("Screens list"), + } + ) return tab_list class IsComAdminMixin(View): - def dispatch(self, request, *args, **kwargs): if not (request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)): raise PermissionDenied @@ -133,34 +160,35 @@ class IsComAdminMixin(View): class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView): model = Sith - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" def get_object(self, queryset=None): return Sith.objects.first() class AlertMsgEditView(ComEditView): - fields = ['alert_msg'] + fields = ["alert_msg"] current_tab = "alert" - success_url = reverse_lazy('com:alert_edit') + success_url = reverse_lazy("com:alert_edit") class InfoMsgEditView(ComEditView): - fields = ['info_msg'] + fields = ["info_msg"] current_tab = "info" - success_url = reverse_lazy('com:info_edit') + success_url = reverse_lazy("com:info_edit") class IndexEditView(ComEditView): - fields = ['index_page'] + fields = ["index_page"] current_tab = "index" - success_url = reverse_lazy('com:index_edit') + success_url = reverse_lazy("com:index_edit") class WeekmailDestinationEditView(ComEditView): - fields = ['weekmail_destinations'] + fields = ["weekmail_destinations"] current_tab = "weekmail_destinations" - success_url = reverse_lazy('com:weekmail_destinations') + success_url = reverse_lazy("com:weekmail_destinations") + # News @@ -168,43 +196,64 @@ class WeekmailDestinationEditView(ComEditView): class NewsForm(forms.ModelForm): class Meta: model = News - fields = ['type', 'title', 'club', 'summary', 'content', 'author'] - widgets = { - 'author': forms.HiddenInput, - 'type': forms.RadioSelect, - } - start_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), widget=SelectDateTime, required=False) - end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), widget=SelectDateTime, required=False) - until = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Until"), widget=SelectDateTime, required=False) + fields = ["type", "title", "club", "summary", "content", "author"] + widgets = {"author": forms.HiddenInput, "type": forms.RadioSelect} + + start_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Start date"), + widget=SelectDateTime, + required=False, + ) + end_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End date"), + widget=SelectDateTime, + required=False, + ) + until = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], label=_("Until"), widget=SelectDateTime, required=False + ) automoderation = forms.BooleanField(label=_("Automoderation"), required=False) def clean(self): self.cleaned_data = super(NewsForm, self).clean() - if self.cleaned_data['type'] != "NOTICE": - if not self.cleaned_data['start_date']: - self.add_error('start_date', ValidationError(_("This field is required."))) - if not self.cleaned_data['end_date']: - self.add_error('end_date', ValidationError(_("This field is required."))) - if self.cleaned_data['start_date'] > self.cleaned_data['end_date']: - self.add_error('end_date', ValidationError(_("You crazy? You can not finish an event before starting it."))) - if self.cleaned_data['type'] == "WEEKLY" and not self.cleaned_data['until']: - self.add_error('until', ValidationError(_("This field is required."))) + if self.cleaned_data["type"] != "NOTICE": + if not self.cleaned_data["start_date"]: + self.add_error( + "start_date", ValidationError(_("This field is required.")) + ) + if not self.cleaned_data["end_date"]: + self.add_error( + "end_date", ValidationError(_("This field is required.")) + ) + if self.cleaned_data["start_date"] > self.cleaned_data["end_date"]: + self.add_error( + "end_date", + ValidationError( + _("You crazy? You can not finish an event before starting it.") + ), + ) + if self.cleaned_data["type"] == "WEEKLY" and not self.cleaned_data["until"]: + self.add_error("until", ValidationError(_("This field is required."))) return self.cleaned_data def save(self): ret = super(NewsForm, self).save() self.instance.dates.all().delete() if self.instance.type == "EVENT" or self.instance.type == "CALL": - NewsDate(start_date=self.cleaned_data['start_date'], - end_date=self.cleaned_data['end_date'], - news=self.instance).save() + NewsDate( + start_date=self.cleaned_data["start_date"], + end_date=self.cleaned_data["end_date"], + news=self.instance, + ).save() elif self.instance.type == "WEEKLY": - start_date = self.cleaned_data['start_date'] - end_date = self.cleaned_data['end_date'] - while start_date <= self.cleaned_data['until']: - NewsDate(start_date=start_date, - end_date=end_date, - news=self.instance).save() + start_date = self.cleaned_data["start_date"] + end_date = self.cleaned_data["end_date"] + while start_date <= self.cleaned_data["until"]: + NewsDate( + start_date=start_date, end_date=end_date, news=self.instance + ).save() start_date += timedelta(days=7) end_date += timedelta(days=7) return ret @@ -213,59 +262,81 @@ class NewsForm(forms.ModelForm): class NewsEditView(CanEditMixin, UpdateView): model = News form_class = NewsForm - template_name = 'com/news_edit.jinja' - pk_url_kwarg = 'news_id' + template_name = "com/news_edit.jinja" + pk_url_kwarg = "news_id" def get_initial(self): init = {} try: - init['start_date'] = self.object.dates.order_by('id').first().start_date.strftime('%Y-%m-%d %H:%M:%S') + init["start_date"] = ( + self.object.dates.order_by("id") + .first() + .start_date.strftime("%Y-%m-%d %H:%M:%S") + ) except: pass try: - init['end_date'] = self.object.dates.order_by('id').first().end_date.strftime('%Y-%m-%d %H:%M:%S') + init["end_date"] = ( + self.object.dates.order_by("id") + .first() + .end_date.strftime("%Y-%m-%d %H:%M:%S") + ) except: pass return init def post(self, request, *args, **kwargs): form = self.get_form() - if form.is_valid() and 'preview' not in request.POST.keys(): + if form.is_valid() and "preview" not in request.POST.keys(): return self.form_valid(form) else: return self.form_invalid(form) def form_valid(self, form): self.object = form.save() - if form.cleaned_data['automoderation'] and self.request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID): + if form.cleaned_data["automoderation"] and self.request.user.is_in_group( + settings.SITH_GROUP_COM_ADMIN_ID + ): self.object.moderator = self.request.user self.object.is_moderated = True self.object.save() else: self.object.is_moderated = False self.object.save() - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - if not u.notifications.filter(type="NEWS_MODERATION", viewed=False).exists(): - Notification(user=u, url=reverse("com:news_detail", kwargs={'news_id': self.object.id}), type="NEWS_MODERATION").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + if not u.notifications.filter( + type="NEWS_MODERATION", viewed=False + ).exists(): + Notification( + user=u, + url=reverse( + "com:news_detail", kwargs={"news_id": self.object.id} + ), + type="NEWS_MODERATION", + ).save() return super(NewsEditView, self).form_valid(form) class NewsCreateView(CanCreateMixin, CreateView): model = News form_class = NewsForm - template_name = 'com/news_edit.jinja' + template_name = "com/news_edit.jinja" def get_initial(self): - init = {'author': self.request.user} + init = {"author": self.request.user} try: - init['club'] = Club.objects.filter(id=self.request.GET['club']).first() + init["club"] = Club.objects.filter(id=self.request.GET["club"]).first() except: pass return init def post(self, request, *args, **kwargs): form = self.get_form() - if form.is_valid() and 'preview' not in request.POST.keys(): + if form.is_valid() and "preview" not in request.POST.keys(): return self.form_valid(form) else: self.object = form.instance @@ -273,176 +344,216 @@ class NewsCreateView(CanCreateMixin, CreateView): def form_valid(self, form): self.object = form.save() - if form.cleaned_data['automoderation'] and self.request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID): + if form.cleaned_data["automoderation"] and self.request.user.is_in_group( + settings.SITH_GROUP_COM_ADMIN_ID + ): self.object.moderator = self.request.user self.object.is_moderated = True self.object.save() else: - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - if not u.notifications.filter(type="NEWS_MODERATION", viewed=False).exists(): - Notification(user=u, url=reverse("com:news_admin_list"), type="NEWS_MODERATION").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + if not u.notifications.filter( + type="NEWS_MODERATION", viewed=False + ).exists(): + Notification( + user=u, + url=reverse("com:news_admin_list"), + type="NEWS_MODERATION", + ).save() return super(NewsCreateView, self).form_valid(form) class NewsDeleteView(CanEditMixin, DeleteView): model = News - pk_url_kwarg = 'news_id' - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('com:news_admin_list') + pk_url_kwarg = "news_id" + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("com:news_admin_list") class NewsModerateView(CanEditMixin, SingleObjectMixin): model = News - pk_url_kwarg = 'news_id' + pk_url_kwarg = "news_id" def get(self, request, *args, **kwargs): self.object = self.get_object() - if 'remove' in request.GET.keys(): + if "remove" in request.GET.keys(): self.object.is_moderated = False else: self.object.is_moderated = True self.object.moderator = request.user self.object.save() - if 'next' in self.request.GET.keys(): - return redirect(self.request.GET['next']) - return redirect('com:news_admin_list') + if "next" in self.request.GET.keys(): + return redirect(self.request.GET["next"]) + return redirect("com:news_admin_list") class NewsAdminListView(CanEditMixin, ListView): model = News - template_name = 'com/news_admin_list.jinja' + template_name = "com/news_admin_list.jinja" queryset = News.objects.all() class NewsListView(CanViewMixin, ListView): model = News - template_name = 'com/news_list.jinja' + template_name = "com/news_list.jinja" queryset = News.objects.filter(is_moderated=True) def get_context_data(self, **kwargs): kwargs = super(NewsListView, self).get_context_data(**kwargs) - kwargs['NewsDate'] = NewsDate - kwargs['timedelta'] = timedelta - kwargs['birthdays'] = User.objects\ - .filter(date_of_birth__month=timezone.now().month, date_of_birth__day=timezone.now().day)\ - .filter(role__in=['STUDENT', 'FORMER STUDENT'])\ - .order_by('-date_of_birth') + kwargs["NewsDate"] = NewsDate + kwargs["timedelta"] = timedelta + kwargs["birthdays"] = ( + User.objects.filter( + date_of_birth__month=timezone.now().month, + date_of_birth__day=timezone.now().day, + ) + .filter(role__in=["STUDENT", "FORMER STUDENT"]) + .order_by("-date_of_birth") + ) return kwargs class NewsDetailView(CanViewMixin, DetailView): model = News - template_name = 'com/news_detail.jinja' - pk_url_kwarg = 'news_id' + template_name = "com/news_detail.jinja" + pk_url_kwarg = "news_id" + # Weekmail class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView): model = Weekmail - template_name = 'com/weekmail_preview.jinja' - success_url = reverse_lazy('com:weekmail') + template_name = "com/weekmail_preview.jinja" + success_url = reverse_lazy("com:weekmail") current_tab = "weekmail" def post(self, request, *args, **kwargs): self.object = self.get_object() try: - if request.POST['send'] == "validate": + if request.POST["send"] == "validate": self.object.send() - return HttpResponseRedirect(reverse('com:weekmail') + "?qn_weekmail_send_success") + return HttpResponseRedirect( + reverse("com:weekmail") + "?qn_weekmail_send_success" + ) except: pass return super(WeekmailEditView, self).get(request, *args, **kwargs) def get_object(self, queryset=None): - return self.model.objects.filter(sent=False).order_by('-id').first() + return self.model.objects.filter(sent=False).order_by("-id").first() def get_context_data(self, **kwargs): """Add rendered weekmail""" kwargs = super(WeekmailPreviewView, self).get_context_data(**kwargs) - kwargs['weekmail_rendered'] = self.object.render_html() + kwargs["weekmail_rendered"] = self.object.render_html() return kwargs class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView): model = Weekmail - template_name = 'com/weekmail.jinja' - form_class = modelform_factory(Weekmail, fields=['title', 'intro', 'joke', 'protip', 'conclusion'], - help_texts={'title': _("Delete and save to regenerate")}) - success_url = reverse_lazy('com:weekmail') + template_name = "com/weekmail.jinja" + form_class = modelform_factory( + Weekmail, + fields=["title", "intro", "joke", "protip", "conclusion"], + help_texts={"title": _("Delete and save to regenerate")}, + ) + success_url = reverse_lazy("com:weekmail") current_tab = "weekmail" def get_object(self, queryset=None): - weekmail = self.model.objects.filter(sent=False).order_by('-id').first() + weekmail = self.model.objects.filter(sent=False).order_by("-id").first() if not weekmail.title: now = timezone.now() - weekmail.title = _("Weekmail of the ") + (now + timedelta(days=6 - now.weekday())).strftime('%d/%m/%Y') + weekmail.title = _("Weekmail of the ") + ( + now + timedelta(days=6 - now.weekday()) + ).strftime("%d/%m/%Y") weekmail.save() return weekmail def get(self, request, *args, **kwargs): self.object = self.get_object() - if 'up_article' in request.GET.keys(): - art = get_object_or_404(WeekmailArticle, id=request.GET['up_article'], weekmail=self.object) - prev_art = self.object.articles.order_by('rank').filter(rank__lt=art.rank).last() + if "up_article" in request.GET.keys(): + art = get_object_or_404( + WeekmailArticle, id=request.GET["up_article"], weekmail=self.object + ) + prev_art = ( + self.object.articles.order_by("rank").filter(rank__lt=art.rank).last() + ) if prev_art: art.rank, prev_art.rank = prev_art.rank, art.rank art.save() prev_art.save() - self.quick_notif_list += ['qn_success'] - if 'down_article' in request.GET.keys(): - art = get_object_or_404(WeekmailArticle, id=request.GET['down_article'], weekmail=self.object) - next_art = self.object.articles.order_by('rank').filter(rank__gt=art.rank).first() + self.quick_notif_list += ["qn_success"] + if "down_article" in request.GET.keys(): + art = get_object_or_404( + WeekmailArticle, id=request.GET["down_article"], weekmail=self.object + ) + next_art = ( + self.object.articles.order_by("rank").filter(rank__gt=art.rank).first() + ) if next_art: art.rank, next_art.rank = next_art.rank, art.rank art.save() next_art.save() - self.quick_notif_list += ['qn_success'] - if 'add_article' in request.GET.keys(): - art = get_object_or_404(WeekmailArticle, id=request.GET['add_article'], weekmail=None) + self.quick_notif_list += ["qn_success"] + if "add_article" in request.GET.keys(): + art = get_object_or_404( + WeekmailArticle, id=request.GET["add_article"], weekmail=None + ) art.weekmail = self.object - art.rank = self.object.articles.aggregate(Max('rank'))['rank__max'] or 0 + art.rank = self.object.articles.aggregate(Max("rank"))["rank__max"] or 0 art.rank += 1 art.save() - self.quick_notif_list += ['qn_success'] - if 'del_article' in request.GET.keys(): - art = get_object_or_404(WeekmailArticle, id=request.GET['del_article'], weekmail=self.object) + self.quick_notif_list += ["qn_success"] + if "del_article" in request.GET.keys(): + art = get_object_or_404( + WeekmailArticle, id=request.GET["del_article"], weekmail=self.object + ) art.weekmail = None art.rank = -1 art.save() - self.quick_notif_list += ['qn_success'] + self.quick_notif_list += ["qn_success"] return super(WeekmailEditView, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): """Add orphan articles """ kwargs = super(WeekmailEditView, self).get_context_data(**kwargs) - kwargs['orphans'] = WeekmailArticle.objects.filter(weekmail=None) + kwargs["orphans"] = WeekmailArticle.objects.filter(weekmail=None) return kwargs -class WeekmailArticleEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView): +class WeekmailArticleEditView( + ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView +): """Edit an article""" + model = WeekmailArticle - fields = ['title', 'club', 'content'] + fields = ["title", "club", "content"] pk_url_kwarg = "article_id" - template_name = 'core/edit.jinja' - success_url = reverse_lazy('com:weekmail') + template_name = "core/edit.jinja" + success_url = reverse_lazy("com:weekmail") quick_notif_url_arg = "qn_weekmail_article_edit" current_tab = "weekmail" class WeekmailArticleCreateView(QuickNotifMixin, CreateView): """Post an article""" + model = WeekmailArticle - fields = ['title', 'club', 'content'] - template_name = 'core/create.jinja' - success_url = reverse_lazy('core:user_tools') + fields = ["title", "club", "content"] + template_name = "core/create.jinja" + success_url = reverse_lazy("core:user_tools") quick_notif_url_arg = "qn_weekmail_new_article" def get_initial(self): init = {} try: - init['club'] = Club.objects.filter(id=self.request.GET['club']).first() + init["club"] = Club.objects.filter(id=self.request.GET["club"]).first() except: pass return init @@ -456,8 +567,15 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView): if m.role <= settings.SITH_MAXIMUM_FREE_ROLE: raise except: - form.add_error('club', ValidationError(_("You must be a board member of the selected club to post in the Weekmail."))) - if form.is_valid() and not 'preview' in request.POST.keys(): + form.add_error( + "club", + ValidationError( + _( + "You must be a board member of the selected club to post in the Weekmail." + ) + ), + ) + if form.is_valid() and not "preview" in request.POST.keys(): return self.form_valid(form) else: return self.form_invalid(form) @@ -469,9 +587,10 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView): class WeekmailArticleDeleteView(CanEditPropMixin, DeleteView): """Delete an article""" + model = WeekmailArticle - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('com:weekmail') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("com:weekmail") pk_url_kwarg = "article_id" @@ -481,40 +600,43 @@ class MailingListAdminView(ComTabsMixin, ListView): current_tab = "mailings" def dispatch(self, request, *args, **kwargs): - if not (request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or request.user.is_root): + if not ( + request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + or request.user.is_root + ): raise PermissionDenied return super(MailingListAdminView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(MailingListAdminView, self).get_context_data(**kwargs) - kwargs['moderated'] = self.get_queryset().filter(is_moderated=True).all() - kwargs['unmoderated'] = self.get_queryset().filter(is_moderated=False).all() - kwargs['has_moderated'] = len(kwargs['moderated']) > 0 - kwargs['has_unmoderated'] = len(kwargs['unmoderated']) > 0 + kwargs["moderated"] = self.get_queryset().filter(is_moderated=True).all() + kwargs["unmoderated"] = self.get_queryset().filter(is_moderated=False).all() + kwargs["has_moderated"] = len(kwargs["moderated"]) > 0 + kwargs["has_unmoderated"] = len(kwargs["unmoderated"]) > 0 return kwargs class MailingModerateView(View): - def get(self, request, *args, **kwargs): - mailing = get_object_or_404(Mailing, pk=kwargs['mailing_id']) + mailing = get_object_or_404(Mailing, pk=kwargs["mailing_id"]) if mailing.can_moderate(request.user): mailing.is_moderated = True mailing.moderator = request.user mailing.save() - return redirect('com:mailing_admin') + return redirect("com:mailing_admin") raise PermissionDenied class PosterListBaseView(ListView): """List communication posters""" + current_tab = "posters" model = Poster - template_name = 'com/poster_list.jinja' + template_name = "com/poster_list.jinja" def dispatch(self, request, *args, **kwargs): - club_id = kwargs.pop('club_id', None) + club_id = kwargs.pop("club_id", None) self.club = None if club_id: self.club = get_object_or_404(Club, pk=club_id) @@ -522,40 +644,41 @@ class PosterListBaseView(ListView): def get_queryset(self): if self.request.user.is_com_admin: - return Poster.objects.all().order_by('-date_begin') + return Poster.objects.all().order_by("-date_begin") else: return Poster.objects.filter(club=self.club.id) def get_context_data(self, **kwargs): kwargs = super(PosterListBaseView, self).get_context_data(**kwargs) if not self.request.user.is_com_admin: - kwargs['club'] = self.club + kwargs["club"] = self.club return kwargs class PosterCreateBaseView(CreateView): """Create communication poster""" + current_tab = "posters" form_class = PosterForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def get_queryset(self): return Poster.objects.all() def dispatch(self, request, *args, **kwargs): - if 'club_id' in kwargs: - self.club = get_object_or_404(Club, pk=kwargs['club_id']) + if "club_id" in kwargs: + self.club = get_object_or_404(Club, pk=kwargs["club_id"]) return super(PosterCreateBaseView, self).dispatch(request, *args, **kwargs) def get_form_kwargs(self): kwargs = super(PosterCreateBaseView, self).get_form_kwargs() - kwargs.update({'user': self.request.user}) + kwargs.update({"user": self.request.user}) return kwargs def get_context_data(self, **kwargs): kwargs = super(PosterCreateBaseView, self).get_context_data(**kwargs) if not self.request.user.is_com_admin: - kwargs['club'] = self.club + kwargs["club"] = self.club return kwargs def form_valid(self, form): @@ -566,27 +689,28 @@ class PosterCreateBaseView(CreateView): class PosterEditBaseView(UpdateView): """Edit communication poster""" + pk_url_kwarg = "poster_id" current_tab = "posters" form_class = PosterForm - template_name = 'com/poster_edit.jinja' + template_name = "com/poster_edit.jinja" def get_initial(self): init = {} try: - init['date_begin'] = self.object.date_begin.strftime('%Y-%m-%d %H:%M:%S') + init["date_begin"] = self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S") except Exception: pass try: - init['date_end'] = self.object.date_end.strftime('%Y-%m-%d %H:%M:%S') + init["date_end"] = self.object.date_end.strftime("%Y-%m-%d %H:%M:%S") except Exception: pass return init def dispatch(self, request, *args, **kwargs): - if 'club_id' in kwargs and kwargs['club_id']: + if "club_id" in kwargs and kwargs["club_id"]: try: - self.club = Club.objects.get(pk=kwargs['club_id']) + self.club = Club.objects.get(pk=kwargs["club_id"]) except Club.DoesNotExist: raise PermissionDenied return super(PosterEditBaseView, self).dispatch(request, *args, **kwargs) @@ -596,13 +720,13 @@ class PosterEditBaseView(UpdateView): def get_form_kwargs(self): kwargs = super(PosterEditBaseView, self).get_form_kwargs() - kwargs.update({'user': self.request.user}) + kwargs.update({"user": self.request.user}) return kwargs def get_context_data(self, **kwargs): kwargs = super(PosterEditBaseView, self).get_context_data(**kwargs) if not self.request.user.is_com_admin: - kwargs['club'] = self.club + kwargs["club"] = self.club return kwargs def form_valid(self, form): @@ -613,15 +737,16 @@ class PosterEditBaseView(UpdateView): class PosterDeleteBaseView(DeleteView): """Edit communication poster""" + pk_url_kwarg = "poster_id" current_tab = "posters" model = Poster - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def dispatch(self, request, *args, **kwargs): - if 'club_id' in kwargs and kwargs['club_id']: + if "club_id" in kwargs and kwargs["club_id"]: try: - self.club = Club.objects.get(pk=kwargs['club_id']) + self.club = Club.objects.get(pk=kwargs["club_id"]) except Club.DoesNotExist: raise PermissionDenied return super(PosterDeleteBaseView, self).dispatch(request, *args, **kwargs) @@ -632,107 +757,117 @@ class PosterListView(IsComAdminMixin, ComTabsMixin, PosterListBaseView): def get_context_data(self, **kwargs): kwargs = super(PosterListView, self).get_context_data(**kwargs) - kwargs['app'] = "com" + kwargs["app"] = "com" return kwargs class PosterCreateView(IsComAdminMixin, ComTabsMixin, PosterCreateBaseView): """Create communication poster""" - success_url = reverse_lazy('com:poster_list') + + success_url = reverse_lazy("com:poster_list") def get_context_data(self, **kwargs): kwargs = super(PosterCreateView, self).get_context_data(**kwargs) - kwargs['app'] = "com" + kwargs["app"] = "com" return kwargs class PosterEditView(IsComAdminMixin, ComTabsMixin, PosterEditBaseView): """Edit communication poster""" - success_url = reverse_lazy('com:poster_list') + + success_url = reverse_lazy("com:poster_list") def get_context_data(self, **kwargs): kwargs = super(PosterEditView, self).get_context_data(**kwargs) - kwargs['app'] = "com" + kwargs["app"] = "com" return kwargs class PosterDeleteView(IsComAdminMixin, ComTabsMixin, PosterDeleteBaseView): """Delete communication poster""" - success_url = reverse_lazy('com:poster_list') + + success_url = reverse_lazy("com:poster_list") class PosterModerateListView(IsComAdminMixin, ComTabsMixin, ListView): """Moderate list communication poster""" + current_tab = "posters" model = Poster - template_name = 'com/poster_moderate.jinja' + template_name = "com/poster_moderate.jinja" queryset = Poster.objects.filter(is_moderated=False).all() def get_context_data(self, **kwargs): kwargs = super(PosterModerateListView, self).get_context_data(**kwargs) - kwargs['app'] = "com" + kwargs["app"] = "com" return kwargs class PosterModerateView(IsComAdminMixin, ComTabsMixin, View): """Moderate communication poster""" + def get(self, request, *args, **kwargs): - obj = get_object_or_404(Poster, pk=kwargs['object_id']) + obj = get_object_or_404(Poster, pk=kwargs["object_id"]) if obj.can_be_moderated_by(request.user): obj.is_moderated = True obj.moderator = request.user obj.save() - return redirect('com:poster_moderate_list') + return redirect("com:poster_moderate_list") raise PermissionDenied def get_context_data(self, **kwargs): kwargs = super(PosterModerateListView, self).get_context_data(**kwargs) - kwargs['app'] = "com" + kwargs["app"] = "com" return kwargs class ScreenListView(IsComAdminMixin, ComTabsMixin, ListView): """List communication screens""" + current_tab = "screens" model = Screen - template_name = 'com/screen_list.jinja' + template_name = "com/screen_list.jinja" class ScreenSlideshowView(DetailView): """Slideshow of actives posters""" + pk_url_kwarg = "screen_id" model = Screen - template_name = 'com/screen_slideshow.jinja' + template_name = "com/screen_slideshow.jinja" def get_context_data(self, **kwargs): kwargs = super(ScreenSlideshowView, self).get_context_data(**kwargs) - kwargs['posters'] = self.object.active_posters() + kwargs["posters"] = self.object.active_posters() return kwargs class ScreenCreateView(IsComAdminMixin, ComTabsMixin, CreateView): """Create communication screen""" + current_tab = "screens" model = Screen - fields = ['name', ] - template_name = 'core/create.jinja' - success_url = reverse_lazy('com:screen_list') + fields = ["name"] + template_name = "core/create.jinja" + success_url = reverse_lazy("com:screen_list") class ScreenEditView(IsComAdminMixin, ComTabsMixin, UpdateView): """Edit communication screen""" + pk_url_kwarg = "screen_id" current_tab = "screens" model = Screen - fields = ['name', ] - template_name = 'com/screen_edit.jinja' - success_url = reverse_lazy('com:screen_list') + fields = ["name"] + template_name = "com/screen_edit.jinja" + success_url = reverse_lazy("com:screen_list") class ScreenDeleteView(IsComAdminMixin, ComTabsMixin, DeleteView): """Delete communication screen""" + pk_url_kwarg = "screen_id" current_tab = "screens" model = Screen - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('com:screen_list') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("com:screen_list") diff --git a/core/__init__.py b/core/__init__.py index ffb9c1e7..e5fb0a2d 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -22,4 +22,4 @@ # # -default_app_config = 'core.apps.SithConfig' +default_app_config = "core.apps.SithConfig" diff --git a/core/admin.py b/core/admin.py index eec0e90b..5b7c9c97 100644 --- a/core/admin.py +++ b/core/admin.py @@ -32,30 +32,38 @@ from haystack.admin import SearchModelAdmin admin.site.unregister(AuthGroup) admin.site.register(RealGroup) + class UserAdmin(SearchModelAdmin): list_display = ["first_name", "last_name", "username", "email", "nick_name"] - form = make_ajax_form(User, { - 'godfathers': 'users', - 'home': 'files', # ManyToManyField - 'profile_pict': 'files', # ManyToManyField - 'avatar_pict': 'files', # ManyToManyField - 'scrub_pict': 'files', # ManyToManyField - }) + form = make_ajax_form( + User, + { + "godfathers": "users", + "home": "files", # ManyToManyField + "profile_pict": "files", # ManyToManyField + "avatar_pict": "files", # ManyToManyField + "scrub_pict": "files", # ManyToManyField + }, + ) search_fields = ["first_name", "last_name", "username"] + admin.site.register(User, UserAdmin) + @admin.register(Page) class PageAdmin(admin.ModelAdmin): - form = make_ajax_form(Page, { - 'lock_user': 'users', - 'owner_group': 'groups', - 'edit_groups': 'groups', - 'view_groups': 'groups', - }) + form = make_ajax_form( + Page, + { + "lock_user": "users", + "owner_group": "groups", + "edit_groups": "groups", + "view_groups": "groups", + }, + ) + @admin.register(SithFile) class SithFileAdmin(admin.ModelAdmin): - form = make_ajax_form(SithFile, { - 'parent': 'files', # ManyToManyField - }) + form = make_ajax_form(SithFile, {"parent": "files"}) # ManyToManyField diff --git a/core/apps.py b/core/apps.py index 75643556..4f3358d5 100644 --- a/core/apps.py +++ b/core/apps.py @@ -29,7 +29,7 @@ from django.core.signals import request_started class SithConfig(AppConfig): - name = 'core' + name = "core" verbose_name = "Core app of the Sith" def ready(self): @@ -47,6 +47,12 @@ class SithConfig(AppConfig): Forum._club_memberships = {} print("Connecting signals!", file=sys.stderr) - request_started.connect(clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups") - request_started.connect(clear_cached_memberships, weak=False, dispatch_uid="clear_cached_memberships") + request_started.connect( + clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups" + ) + request_started.connect( + clear_cached_memberships, + weak=False, + dispatch_uid="clear_cached_memberships", + ) # TODO: there may be a need to add more cache clearing diff --git a/core/lookups.py b/core/lookups.py index 3a77e915..477ef95e 100644 --- a/core/lookups.py +++ b/core/lookups.py @@ -33,9 +33,11 @@ from accounting.models import ClubAccount, Company def check_token(request): - return ('counter_token' in request.session.keys() and - request.session['counter_token'] and - Counter.objects.filter(token=request.session['counter_token']).exists()) + return ( + "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter(token=request.session["counter_token"]).exists() + ) class RightManagedLookupChannel(LookupChannel): @@ -44,7 +46,7 @@ class RightManagedLookupChannel(LookupChannel): raise PermissionDenied -@register('users') +@register("users") class UsersLookup(RightManagedLookupChannel): model = User @@ -58,7 +60,7 @@ class UsersLookup(RightManagedLookupChannel): return item.get_display_name() -@register('groups') +@register("groups") class GroupsLookup(RightManagedLookupChannel): model = Group @@ -72,7 +74,7 @@ class GroupsLookup(RightManagedLookupChannel): return item.name -@register('clubs') +@register("clubs") class ClubLookup(RightManagedLookupChannel): model = Club @@ -86,7 +88,7 @@ class ClubLookup(RightManagedLookupChannel): return item.name -@register('counters') +@register("counters") class CountersLookup(RightManagedLookupChannel): model = Counter @@ -97,19 +99,21 @@ class CountersLookup(RightManagedLookupChannel): return item.name -@register('products') +@register("products") class ProductsLookup(RightManagedLookupChannel): model = Product def get_query(self, q, request): - return (self.model.objects.filter(name__icontains=q) | - self.model.objects.filter(code__icontains=q)).filter(archived=False)[:50] + return ( + self.model.objects.filter(name__icontains=q) + | self.model.objects.filter(code__icontains=q) + ).filter(archived=False)[:50] def format_item_display(self, item): return "%s (%s)" % (item.name, item.code) -@register('files') +@register("files") class SithFileLookup(RightManagedLookupChannel): model = SithFile @@ -117,7 +121,7 @@ class SithFileLookup(RightManagedLookupChannel): return self.model.objects.filter(name__icontains=q)[:50] -@register('club_accounts') +@register("club_accounts") class ClubAccountLookup(RightManagedLookupChannel): model = ClubAccount @@ -128,7 +132,7 @@ class ClubAccountLookup(RightManagedLookupChannel): return item.name -@register('companies') +@register("companies") class CompaniesLookup(RightManagedLookupChannel): model = Company diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py index 0a9419f8..0ace29c4 100644 --- a/core/management/commands/__init__.py +++ b/core/management/commands/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/core/management/commands/check_fs.py b/core/management/commands/check_fs.py index 19943e02..7ebc076d 100644 --- a/core/management/commands/check_fs.py +++ b/core/management/commands/check_fs.py @@ -33,10 +33,14 @@ class Command(BaseCommand): help = "Recursively check the file system with respect to the DB" def add_arguments(self, parser): - parser.add_argument('ids', metavar='ID', type=int, nargs='+', help="The file IDs to process") + parser.add_argument( + "ids", metavar="ID", type=int, nargs="+", help="The file IDs to process" + ) def handle(self, *args, **options): - root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - files = SithFile.objects.filter(id__in=options['ids']).all() + root_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) + files = SithFile.objects.filter(id__in=options["ids"]).all() for f in files: f._check_fs() diff --git a/core/management/commands/compilestatic.py b/core/management/commands/compilestatic.py index c634bc35..fd956333 100644 --- a/core/management/commands/compilestatic.py +++ b/core/management/commands/compilestatic.py @@ -33,15 +33,13 @@ class Command(BaseCommand): """ Compiles scss in static folder for production """ + help = "Compile scss files from static folder" def compile(self, filename): - args = { - "filename": filename, - "include_paths": settings.STATIC_ROOT, - } + args = {"filename": filename, "include_paths": settings.STATIC_ROOT} if settings.SASS_PRECISION: - args['precision'] = settings.SASS_PRECISION + args["precision"] = settings.SASS_PRECISION return sass.compile(**args) def is_compilable(self, file, ext_list): @@ -54,7 +52,7 @@ class Command(BaseCommand): file = os.path.join(folder, file) if os.path.isdir(file): self.exec_on_folder(file, func) - elif self.is_compilable(file, ['.scss']): + elif self.is_compilable(file, [".scss"]): to_exec.append(file) for file in to_exec: @@ -62,7 +60,7 @@ class Command(BaseCommand): def compilescss(self, file): print("compiling %s" % file) - with(open(file.replace('.scss', '.css'), "w")) as newfile: + with (open(file.replace(".scss", ".css"), "w")) as newfile: newfile.write(self.compile(file)) def removescss(self, file): @@ -77,4 +75,6 @@ class Command(BaseCommand): print("---- Removing scss files ----") self.exec_on_folder(settings.STATIC_ROOT, self.removescss) else: - print("No static folder avalaible, please use collectstatic before compiling scss") + print( + "No static folder avalaible, please use collectstatic before compiling scss" + ) diff --git a/core/management/commands/markdown.py b/core/management/commands/markdown.py index d0fad56f..1b5a6855 100644 --- a/core/management/commands/markdown.py +++ b/core/management/commands/markdown.py @@ -27,11 +27,14 @@ from django.core.management.base import BaseCommand from core.markdown import markdown + class Command(BaseCommand): help = "Output the fully rendered doc/SYNTAX.md file" def handle(self, *args, **options): - root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as md: + root_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) + with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md: result = markdown(md.read()) - print(result, end='') + print(result, end="") diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index e76dfb02..d3356d3e 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -36,7 +36,15 @@ from django.utils import timezone from PIL import Image from core.models import Group, User, Page, PageRev, SithFile -from accounting.models import GeneralJournal, BankAccount, ClubAccount, Operation, AccountingType, SimplifiedAccountingType, Company +from accounting.models import ( + GeneralJournal, + BankAccount, + ClubAccount, + Operation, + AccountingType, + SimplifiedAccountingType, + Company, +) from core.utils import resize_image from club.models import Club, Membership from subscription.models import Subscription @@ -50,7 +58,7 @@ class Command(BaseCommand): help = "Populate a new instance of the Sith AE" def add_arguments(self, parser): - parser.add_argument('--prod', action="store_true") + parser.add_argument("--prod", action="store_true") def reset_index(self, *args): sqlcmd = StringIO() @@ -59,9 +67,11 @@ class Command(BaseCommand): cursor.execute(sqlcmd.getvalue()) def handle(self, *args, **options): - os.environ['DJANGO_COLORS'] = 'nocolor' + os.environ["DJANGO_COLORS"] = "nocolor" Site(id=4000, domain=settings.SITH_URL, name=settings.SITH_NAME).save() - root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) + root_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) Group(name="Root").save() Group(name="Public").save() Group(name="Subscribers").save() @@ -75,13 +85,21 @@ class Command(BaseCommand): Group(name="SAS admin").save() Group(name="Forum admin").save() self.reset_index("core", "auth") - root = User(id=0, username='root', last_name="", first_name="Bibou", - email="ae.info@utbm.fr", - date_of_birth="1942-06-12", - is_superuser=True, is_staff=True) + root = User( + id=0, + username="root", + last_name="", + first_name="Bibou", + email="ae.info@utbm.fr", + date_of_birth="1942-06-12", + is_superuser=True, + is_staff=True, + ) root.set_password("plop") root.save() - profiles_root = SithFile(parent=None, name="profiles", is_folder=True, owner=root) + profiles_root = SithFile( + parent=None, name="profiles", is_folder=True, owner=root + ) profiles_root.save() home_root = SithFile(parent=None, name="users", is_folder=True, owner=root) home_root.save() @@ -94,250 +112,395 @@ class Command(BaseCommand): club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root) club_root.save() SithFile(parent=None, name="SAS", is_folder=True, owner=root).save() - main_club = Club(id=1, name=settings.SITH_MAIN_CLUB['name'], unix_name=settings.SITH_MAIN_CLUB['unix_name'], - address=settings.SITH_MAIN_CLUB['address']) + main_club = Club( + id=1, + name=settings.SITH_MAIN_CLUB["name"], + unix_name=settings.SITH_MAIN_CLUB["unix_name"], + address=settings.SITH_MAIN_CLUB["address"], + ) main_club.save() - bar_club = Club(id=2, name=settings.SITH_BAR_MANAGER['name'], unix_name=settings.SITH_BAR_MANAGER['unix_name'], - address=settings.SITH_BAR_MANAGER['address']) + bar_club = Club( + id=2, + name=settings.SITH_BAR_MANAGER["name"], + unix_name=settings.SITH_BAR_MANAGER["unix_name"], + address=settings.SITH_BAR_MANAGER["address"], + ) bar_club.save() - launderette_club = Club(id=84, name=settings.SITH_LAUNDERETTE_MANAGER['name'], - unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name'], - address=settings.SITH_LAUNDERETTE_MANAGER['address']) + launderette_club = Club( + id=84, + name=settings.SITH_LAUNDERETTE_MANAGER["name"], + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"], + address=settings.SITH_LAUNDERETTE_MANAGER["address"], + ) launderette_club.save() self.reset_index("club") for b in settings.SITH_COUNTER_BARS: g = Group(name=b[1] + " admin") g.save() - c = Counter(id=b[0], name=b[1], club=bar_club, type='BAR') + c = Counter(id=b[0], name=b[1], club=bar_club, type="BAR") c.save() c.edit_groups = [g] c.save() self.reset_index("counter") - Counter(name="Eboutic", club=main_club, type='EBOUTIC').save() - Counter(name="AE", club=main_club, type='OFFICE').save() + Counter(name="Eboutic", club=main_club, type="EBOUTIC").save() + Counter(name="AE", club=main_club, type="OFFICE").save() - home_root.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()] - club_root.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()] + home_root.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first() + ] + club_root.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first() + ] home_root.save() club_root.save() Sith(weekmail_destinations="etudiants@git.an personnel@git.an").save() Weekmail().save() - p = Page(name='Index') + p = Page(name="Index") p.set_lock(root) p.save() p.view_groups = [settings.SITH_GROUP_PUBLIC_ID] p.set_lock(root) p.save() - PageRev(page=p, title="Wiki index", author=root, content=""" + PageRev( + page=p, + title="Wiki index", + author=root, + content=""" Welcome to the wiki page! -""").save() +""", + ).save() p = Page(name="services") p.set_lock(root) p.save() p.view_groups = [settings.SITH_GROUP_PUBLIC_ID] p.set_lock(root) - PageRev(page=p, title="Services", author=root, content=""" + PageRev( + page=p, + title="Services", + author=root, + content=""" | | | | | :---: | :---: | :---: | :---: | | [Eboutic](/eboutic) | [Laverie](/launderette) | Matmat | [Fichiers](/file) | | SAS | Weekmail | Forum | | -""").save() +""", + ).save() p = Page(name="launderette") p.set_lock(root) p.save() p.set_lock(root) - PageRev(page=p, title="Laverie", author=root, content="Fonctionnement de la laverie").save() + PageRev( + page=p, title="Laverie", author=root, content="Fonctionnement de la laverie" + ).save() # Here we add a lot of test datas, that are not necessary for the Sith, but that provide a basic development environment - if not options['prod']: + if not options["prod"]: # Adding user Skia - skia = User(username='skia', last_name="Kia", first_name="S'", - email="skia@git.an", - date_of_birth="1942-06-12") + skia = User( + username="skia", + last_name="Kia", + first_name="S'", + email="skia@git.an", + date_of_birth="1942-06-12", + ) skia.set_password("plop") skia.save() - skia.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + skia.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] skia.save() - skia_profile_path = os.path.join(root_path, 'core/fixtures/images/3.jpg') - with open(skia_profile_path, 'rb') as f: + skia_profile_path = os.path.join(root_path, "core/fixtures/images/3.jpg") + with open(skia_profile_path, "rb") as f: name = str(skia.id) + "_profile.jpg" - skia_profile = SithFile(parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), - owner=skia, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(skia_profile_path)) + skia_profile = SithFile( + parent=profiles_root, + name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + owner=skia, + is_folder=False, + mime_type="image/jpeg", + size=os.path.getsize(skia_profile_path), + ) skia_profile.file.name = name skia_profile.save() skia.profile_pict = skia_profile skia.save() # Adding user public - public = User(username='public', last_name="Not subscribed", first_name="Public", - email="public@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + public = User( + username="public", + last_name="Not subscribed", + first_name="Public", + email="public@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) public.set_password("plop") public.save() - public.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + public.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] public.save() # Adding user Subscriber - subscriber = User(username='subscriber', last_name="User", first_name="Subscribed", - email="Subscribed@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + subscriber = User( + username="subscriber", + last_name="User", + first_name="Subscribed", + email="Subscribed@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) subscriber.set_password("plop") subscriber.save() - subscriber.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + subscriber.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] subscriber.save() # Adding user old Subscriber - old_subscriber = User(username='old_subscriber', last_name="Subscriber", first_name="Old", - email="old_subscriber@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + old_subscriber = User( + username="old_subscriber", + last_name="Subscriber", + first_name="Old", + email="old_subscriber@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) old_subscriber.set_password("plop") old_subscriber.save() - old_subscriber.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + old_subscriber.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] old_subscriber.save() # Adding user Counter admin - counter = User(username='counter', last_name="Ter", first_name="Coun", - email="counter@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + counter = User( + username="counter", + last_name="Ter", + first_name="Coun", + email="counter@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) counter.set_password("plop") counter.save() - counter.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] - counter.groups = [Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID).first().id] + counter.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] + counter.groups = [ + Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID).first().id + ] counter.save() # Adding user Comptable - comptable = User(username='comptable', last_name="Able", first_name="Compte", - email="compta@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + comptable = User( + username="comptable", + last_name="Able", + first_name="Compte", + email="compta@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) comptable.set_password("plop") comptable.save() - comptable.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] - comptable.groups = [Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID).first().id] + comptable.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] + comptable.groups = [ + Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) + .first() + .id + ] comptable.save() # Adding user Guy - u = User(username='guy', last_name="Carlier", first_name="Guy", - email="guy@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + u = User( + username="guy", + last_name="Carlier", + first_name="Guy", + email="guy@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) u.set_password("plop") u.save() - u.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + u.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] u.save() # Adding user Richard Batsbak - r = User(username='rbatsbak', last_name="Batsbak", first_name="Richard", - email="richard@git.an", - date_of_birth="1982-06-12") + r = User( + username="rbatsbak", + last_name="Batsbak", + first_name="Richard", + email="richard@git.an", + date_of_birth="1982-06-12", + ) r.set_password("plop") r.save() - r.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + r.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] r.save() # Adding syntax help page - p = Page(name='Aide_sur_la_syntaxe') + p = Page(name="Aide_sur_la_syntaxe") p.save(force_lock=True) - with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as rm: - PageRev(page=p, title="Aide sur la syntaxe", author=skia, content=rm.read()).save() + with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as rm: + PageRev( + page=p, title="Aide sur la syntaxe", author=skia, content=rm.read() + ).save() p.view_groups = [settings.SITH_GROUP_PUBLIC_ID] p.save(force_lock=True) - p = Page(name='Services') + p = Page(name="Services") p.save(force_lock=True) p.view_groups = [settings.SITH_GROUP_PUBLIC_ID] p.save(force_lock=True) - PageRev(page=p, title="Services", author=skia, content=""" + PageRev( + page=p, + title="Services", + author=skia, + content=""" | | | | | :---: | :---: | :---: | | [Eboutic](/eboutic) | [Laverie](/launderette) | Matmat | | SAS | Weekmail | Forum| -""").save() +""", + ).save() # Adding README - p = Page(name='README') + p = Page(name="README") p.save(force_lock=True) p.view_groups = [settings.SITH_GROUP_PUBLIC_ID] p.save(force_lock=True) - with open(os.path.join(root_path) + '/README.md', 'r') as rm: + with open(os.path.join(root_path) + "/README.md", "r") as rm: PageRev(page=p, title="README", author=skia, content=rm.read()).save() # Subscription - default_subscription = 'un-semestre' + default_subscription = "un-semestre" # Root - s = Subscription(member=User.objects.filter(pk=root.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=root.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Skia - s = Subscription(member=User.objects.filter(pk=skia.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=skia.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Counter admin - s = Subscription(member=User.objects.filter(pk=counter.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=counter.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Comptable - s = Subscription(member=User.objects.filter(pk=comptable.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=comptable.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Richard - s = Subscription(member=User.objects.filter(pk=r.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=r.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # User - s = Subscription(member=User.objects.filter(pk=subscriber.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=subscriber.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Old subscriber - s = Subscription(member=User.objects.filter(pk=old_subscriber.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=old_subscriber.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start(datetime(year=2012, month=9, day=4)) s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Clubs - Club(name="Bibo'UT", unix_name="bibout", - address="46 de la Boustifaille", parent=main_club).save() - guyut = Club(name="Guy'UT", unix_name="guyut", - address="42 de la Boustifaille", parent=main_club) + Club( + name="Bibo'UT", + unix_name="bibout", + address="46 de la Boustifaille", + parent=main_club, + ).save() + guyut = Club( + name="Guy'UT", + unix_name="guyut", + address="42 de la Boustifaille", + parent=main_club, + ) guyut.save() - Club(name="Woenzel'UT", unix_name="woenzel", - address="Woenzel", parent=guyut).save() + Club( + name="Woenzel'UT", unix_name="woenzel", address="Woenzel", parent=guyut + ).save() Membership(user=skia, club=main_club, role=3, description="").save() - troll = Club(name="Troll Penché", unix_name="troll", - address="Terre Du Milieu", parent=main_club) + troll = Club( + name="Troll Penché", + unix_name="troll", + address="Terre Du Milieu", + parent=main_club, + ) troll.save() - refound = Club(name="Carte AE", unix_name="carte_ae", - address="Jamais imprimée", parent=main_club) + refound = Club( + name="Carte AE", + unix_name="carte_ae", + address="Jamais imprimée", + parent=main_club, + ) refound.save() # Counters @@ -351,33 +514,96 @@ Welcome to the wiki page! r.save() verre = ProductType(name="Verre") verre.save() - cotis = Product(name="Cotis 1 semestre", code="1SCOTIZ", product_type=c, purchase_price="15", selling_price="15", - special_selling_price="15", club=main_club) + cotis = Product( + name="Cotis 1 semestre", + code="1SCOTIZ", + product_type=c, + purchase_price="15", + selling_price="15", + special_selling_price="15", + club=main_club, + ) cotis.save() - cotis2 = Product(name="Cotis 2 semestres", code="2SCOTIZ", product_type=c, purchase_price="28", selling_price="28", - special_selling_price="28", club=main_club) + cotis2 = Product( + name="Cotis 2 semestres", + code="2SCOTIZ", + product_type=c, + purchase_price="28", + selling_price="28", + special_selling_price="28", + club=main_club, + ) cotis2.save() - refill = Product(name="Rechargement 15 €", code="15REFILL", product_type=r, purchase_price="15", selling_price="15", - special_selling_price="15", club=main_club) + refill = Product( + name="Rechargement 15 €", + code="15REFILL", + product_type=r, + purchase_price="15", + selling_price="15", + special_selling_price="15", + club=main_club, + ) refill.save() - barb = Product(name="Barbar", code="BARB", product_type=p, purchase_price="1.50", selling_price="1.7", - special_selling_price="1.6", club=main_club) + barb = Product( + name="Barbar", + code="BARB", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + ) barb.save() - cble = Product(name="Chimay Bleue", code="CBLE", product_type=p, purchase_price="1.50", selling_price="1.7", - special_selling_price="1.6", club=main_club) + cble = Product( + name="Chimay Bleue", + code="CBLE", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + ) cble.save() - cons = Product(name="Consigne Eco-cup", code="CONS", product_type=verre, purchase_price="1", selling_price="1", - special_selling_price="1", club=main_club) + cons = Product( + name="Consigne Eco-cup", + code="CONS", + product_type=verre, + purchase_price="1", + selling_price="1", + special_selling_price="1", + club=main_club, + ) cons.id = 1152 cons.save() - dcons = Product(name="Déconsigne Eco-cup", code="DECO", product_type=verre, purchase_price="-1", selling_price="-1", - special_selling_price="-1", club=main_club) + dcons = Product( + name="Déconsigne Eco-cup", + code="DECO", + product_type=verre, + purchase_price="-1", + selling_price="-1", + special_selling_price="-1", + club=main_club, + ) dcons.id = 1151 dcons.save() - Product(name="Corsendonk", code="CORS", product_type=p, purchase_price="1.50", selling_price="1.7", - special_selling_price="1.6", club=main_club).save() - Product(name="Carolus", code="CARO", product_type=p, purchase_price="1.50", selling_price="1.7", - special_selling_price="1.6", club=main_club).save() + Product( + name="Corsendonk", + code="CORS", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + ).save() + Product( + name="Carolus", + code="CARO", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + ).save() mde = Counter.objects.filter(name="MDE").first() mde.products.add(barb) mde.products.add(cble) @@ -393,10 +619,16 @@ Welcome to the wiki page! eboutic.products.add(refill) eboutic.save() - refound_counter = Counter(name="Carte AE", club=refound, type='OFFICE') + refound_counter = Counter(name="Carte AE", club=refound, type="OFFICE") refound_counter.save() - refound_product = Product(name="remboursement", code="REMBOURS", purchase_price="0", selling_price="0", - special_selling_price="0", club=refound) + refound_product = Product( + name="remboursement", + code="REMBOURS", + purchase_price="0", + selling_price="0", + special_selling_price="0", + club=refound, + ) refound_product.save() # Accounting test values: @@ -408,94 +640,238 @@ Welcome to the wiki page! ca.save() gj = GeneralJournal(name="A16", start_date=date.today(), club_account=ca) gj.save() - credit = AccountingType(code='74', label="Subventions d'exploitation", movement_type='CREDIT') + credit = AccountingType( + code="74", label="Subventions d'exploitation", movement_type="CREDIT" + ) credit.save() - debit = AccountingType(code='606', label="Achats non stockés de matières et fournitures(*1)", movement_type='DEBIT') + debit = AccountingType( + code="606", + label="Achats non stockés de matières et fournitures(*1)", + movement_type="DEBIT", + ) debit.save() - debit2 = AccountingType(code='604', label="Achats d'études et prestations de services(*2)", movement_type='DEBIT') + debit2 = AccountingType( + code="604", + label="Achats d'études et prestations de services(*2)", + movement_type="DEBIT", + ) debit2.save() - buying = AccountingType(code='60', label="Achats (sauf 603)", movement_type='DEBIT') + buying = AccountingType( + code="60", label="Achats (sauf 603)", movement_type="DEBIT" + ) buying.save() - comptes = AccountingType(code='6', label="Comptes de charge", movement_type='DEBIT') + comptes = AccountingType( + code="6", label="Comptes de charge", movement_type="DEBIT" + ) comptes.save() - simple = SimplifiedAccountingType(label='Je fais du simple 6', accounting_type=comptes) + simple = SimplifiedAccountingType( + label="Je fais du simple 6", accounting_type=comptes + ) simple.save() woenzco = Company(name="Woenzel & co") woenzco.save() operation_list = [ - (27, "J'avais trop de bière", 'CASH', None, buying, 'USER', skia.id, "", None), - (4000, "Ceci n'est pas une opération... en fait si mais non", 'CHECK', None, debit, 'COMPANY', woenzco.id, "", 23), - (22, "C'est de l'argent ?", 'CARD', None, credit, 'CLUB', troll.id, "", None), - (37, "Je paye CASH", 'CASH', None, debit2, 'OTHER', None, "tous les étudiants <3", None), - (300, "Paiement Guy", 'CASH', None, buying, 'USER', skia.id, "", None), - (32.3, "Essence", 'CASH', None, buying, 'OTHER', None, "station", None), - (46.42, "Allumette", 'CHECK', None, credit, 'CLUB', main_club.id, "", 57), - (666.42, "Subvention de far far away", 'CASH', None, comptes, 'CLUB', main_club.id, "", None), - (496, "Ça, c'est un 6", 'CARD', simple, None, 'USER', skia.id, "", None), - (17, "La Gargotte du Korrigan", 'CASH', None, debit2, 'CLUB', bar_club.id, "", None), + ( + 27, + "J'avais trop de bière", + "CASH", + None, + buying, + "USER", + skia.id, + "", + None, + ), + ( + 4000, + "Ceci n'est pas une opération... en fait si mais non", + "CHECK", + None, + debit, + "COMPANY", + woenzco.id, + "", + 23, + ), + ( + 22, + "C'est de l'argent ?", + "CARD", + None, + credit, + "CLUB", + troll.id, + "", + None, + ), + ( + 37, + "Je paye CASH", + "CASH", + None, + debit2, + "OTHER", + None, + "tous les étudiants <3", + None, + ), + (300, "Paiement Guy", "CASH", None, buying, "USER", skia.id, "", None), + (32.3, "Essence", "CASH", None, buying, "OTHER", None, "station", None), + ( + 46.42, + "Allumette", + "CHECK", + None, + credit, + "CLUB", + main_club.id, + "", + 57, + ), + ( + 666.42, + "Subvention de far far away", + "CASH", + None, + comptes, + "CLUB", + main_club.id, + "", + None, + ), + ( + 496, + "Ça, c'est un 6", + "CARD", + simple, + None, + "USER", + skia.id, + "", + None, + ), + ( + 17, + "La Gargotte du Korrigan", + "CASH", + None, + debit2, + "CLUB", + bar_club.id, + "", + None, + ), ] for op in operation_list: - operation = Operation(journal=gj, date=date.today(), amount=op[0], - remark=op[1], mode=op[2], done=True, simpleaccounting_type=op[3], - accounting_type=op[4], target_type=op[5], target_id=op[6], - target_label=op[7], cheque_number=op[8]) + operation = Operation( + journal=gj, + date=date.today(), + amount=op[0], + remark=op[1], + mode=op[2], + done=True, + simpleaccounting_type=op[3], + accounting_type=op[4], + target_type=op[5], + target_id=op[6], + target_label=op[7], + cheque_number=op[8], + ) operation.clean() operation.save() # Adding user sli - sli = User(username='sli', last_name="Li", first_name="S", - email="sli@git.an", - date_of_birth="1942-06-12") + sli = User( + username="sli", + last_name="Li", + first_name="S", + email="sli@git.an", + date_of_birth="1942-06-12", + ) sli.set_password("plop") sli.save() - sli.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + sli.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] sli.save() - sli_profile_path = os.path.join(root_path, 'core/fixtures/images/5.jpg') - with open(sli_profile_path, 'rb') as f: + sli_profile_path = os.path.join(root_path, "core/fixtures/images/5.jpg") + with open(sli_profile_path, "rb") as f: name = str(sli.id) + "_profile.jpg" - sli_profile = SithFile(parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), - owner=sli, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(sli_profile_path)) + sli_profile = SithFile( + parent=profiles_root, + name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + owner=sli, + is_folder=False, + mime_type="image/jpeg", + size=os.path.getsize(sli_profile_path), + ) sli_profile.file.name = name sli_profile.save() sli.profile_pict = sli_profile sli.save() # Adding user Krophil - krophil = User(username='krophil', last_name="Phil'", first_name="Kro", - email="krophil@git.an", - date_of_birth="1942-06-12") + krophil = User( + username="krophil", + last_name="Phil'", + first_name="Kro", + email="krophil@git.an", + date_of_birth="1942-06-12", + ) krophil.set_password("plop") krophil.save() - krophil_profile_path = os.path.join(root_path, 'core/fixtures/images/6.jpg') - with open(krophil_profile_path, 'rb') as f: + krophil_profile_path = os.path.join(root_path, "core/fixtures/images/6.jpg") + with open(krophil_profile_path, "rb") as f: name = str(krophil.id) + "_profile.jpg" - krophil_profile = SithFile(parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), - owner=krophil, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(krophil_profile_path)) + krophil_profile = SithFile( + parent=profiles_root, + name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + owner=krophil, + is_folder=False, + mime_type="image/jpeg", + size=os.path.getsize(krophil_profile_path), + ) krophil_profile.file.name = name krophil_profile.save() krophil.profile_pict = krophil_profile krophil.save() # Adding subscription for sli - s = Subscription(member=User.objects.filter(pk=sli.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=sli.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Adding subscription for Krophil - s = Subscription(member=User.objects.filter(pk=krophil.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=krophil.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() - Selling(label=dcons.name, product=dcons, counter=mde, unit_price=dcons.selling_price, club=main_club, - quantity=settings.SITH_ECOCUP_LIMIT + 3, seller=skia, customer=krophil.customer).save() + Selling( + label=dcons.name, + product=dcons, + counter=mde, + unit_price=dcons.selling_price, + club=main_club, + quantity=settings.SITH_ECOCUP_LIMIT + 3, + seller=skia, + customer=krophil.customer, + ).save() # Add barman to counter c = Counter.objects.get(id=2) @@ -506,9 +882,14 @@ Welcome to the wiki page! public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID) subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) - el = Election(title="Élection 2017", description="La roue tourne", start_candidature='1942-06-12 10:28:45+01', - end_candidature='2042-06-12 10:28:45+01', start_date='1942-06-12 10:28:45+01', - end_date='7942-06-12 10:28:45+01') + el = Election( + title="Élection 2017", + description="La roue tourne", + start_candidature="1942-06-12 10:28:45+01", + end_candidature="2042-06-12 10:28:45+01", + start_date="1942-06-12 10:28:45+01", + end_date="7942-06-12 10:28:45+01", + ) el.save() el.view_groups.add(public_group) el.edit_groups.add(ae_board_group) @@ -521,27 +902,55 @@ Welcome to the wiki page! listeT.save() pres = Role(election=el, title="Président AE", description="Roi de l'AE") pres.save() - resp = Role(election=el, title="Co Respo Info", max_choice=2, description="Ghetto++") + resp = Role( + election=el, title="Co Respo Info", max_choice=2, description="Ghetto++" + ) resp.save() - cand = Candidature(role=resp, user=skia, election_list=liste, program="Refesons le site AE") + cand = Candidature( + role=resp, user=skia, election_list=liste, program="Refesons le site AE" + ) cand.save() - cand = Candidature(role=resp, user=sli, election_list=liste, program="Vasy je deviens mon propre adjoint") + cand = Candidature( + role=resp, + user=sli, + election_list=liste, + program="Vasy je deviens mon propre adjoint", + ) cand.save() - cand = Candidature(role=resp, user=krophil, election_list=listeT, program="Le Pôle Troll !") + cand = Candidature( + role=resp, user=krophil, election_list=listeT, program="Le Pôle Troll !" + ) cand.save() - cand = Candidature(role=pres, user=sli, election_list=listeT, program="En fait j'aime pas l'info, je voulais faire GMC") + cand = Candidature( + role=pres, + user=sli, + election_list=listeT, + program="En fait j'aime pas l'info, je voulais faire GMC", + ) cand.save() # Forum - room = Forum(name="Salon de discussions", description="Pour causer de tout", is_category=True) + room = Forum( + name="Salon de discussions", + description="Pour causer de tout", + is_category=True, + ) room.save() Forum(name="AE", description="Réservé au bureau AE", parent=room).save() Forum(name="BdF", description="Réservé au bureau BdF", parent=room).save() - hall = Forum(name="Hall de discussions", description="Pour toutes les discussions", parent=room) + hall = Forum( + name="Hall de discussions", + description="Pour toutes les discussions", + parent=room, + ) hall.save() - various = Forum(name="Divers", description="Pour causer de rien", is_category=True) + various = Forum( + name="Divers", description="Pour causer de rien", is_category=True + ) various.save() - Forum(name="Promos", description="Réservé aux Promos", parent=various).save() + Forum( + name="Promos", description="Réservé aux Promos", parent=various + ).save() ForumTopic(forum=hall) # News @@ -550,39 +959,87 @@ Welcome to the wiki page! friday += timedelta(hours=6) friday.replace(hour=20, minute=0, second=0) # Event - n = News(title="Apero barman", summary="Viens boire un coup avec les barmans", - content="Glou glou glou glou glou glou glou" , type="EVENT", - club=bar_club, author=subscriber, is_moderated=True, moderator=skia) + n = News( + title="Apero barman", + summary="Viens boire un coup avec les barmans", + content="Glou glou glou glou glou glou glou", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) n.save() - NewsDate(news=n, start_date=timezone.now()+timedelta(hours=70), - end_date=timezone.now()+timedelta(hours=72)).save() - n = News(title="Repas barman", summary="Enjoy la fin du semestre!", - content="Viens donc t'enjailler avec les autres barmans aux " - "frais du BdF! \o/", type="EVENT", club=bar_club, - author=subscriber, is_moderated=True, moderator=skia) + NewsDate( + news=n, + start_date=timezone.now() + timedelta(hours=70), + end_date=timezone.now() + timedelta(hours=72), + ).save() + n = News( + title="Repas barman", + summary="Enjoy la fin du semestre!", + content="Viens donc t'enjailler avec les autres barmans aux " + "frais du BdF! \o/", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) n.save() - NewsDate(news=n, start_date=timezone.now()+timedelta(hours=72), - end_date=timezone.now()+timedelta(hours=84)).save() - n = News(title="Repas fromager", summary="Wien manger du l'bon fromeug'", - content="Fô viendre mangey d'la bonne fondue!", - type="EVENT", club=bar_club, author=subscriber, - is_moderated=True, moderator=skia) + NewsDate( + news=n, + start_date=timezone.now() + timedelta(hours=72), + end_date=timezone.now() + timedelta(hours=84), + ).save() + n = News( + title="Repas fromager", + summary="Wien manger du l'bon fromeug'", + content="Fô viendre mangey d'la bonne fondue!", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) n.save() - NewsDate(news=n, start_date=timezone.now()+timedelta(hours=96), - end_date=timezone.now()+timedelta(hours=100)).save() - n = News(title="SdF", summary="Enjoy la fin des finaux!", - content="Viens faire la fête avec tout plein de gens!", - type="EVENT", club=bar_club, author=subscriber, - is_moderated=True, moderator=skia) + NewsDate( + news=n, + start_date=timezone.now() + timedelta(hours=96), + end_date=timezone.now() + timedelta(hours=100), + ).save() + n = News( + title="SdF", + summary="Enjoy la fin des finaux!", + content="Viens faire la fête avec tout plein de gens!", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) n.save() - NewsDate(news=n, start_date=friday+timedelta(hours=24*7+1), - end_date=timezone.now()+timedelta(hours=24*7+9)).save() + NewsDate( + news=n, + start_date=friday + timedelta(hours=24 * 7 + 1), + end_date=timezone.now() + timedelta(hours=24 * 7 + 9), + ).save() # Weekly - n = News(title="Jeux sans faim", summary="Viens jouer!", - content="Rejoins la fine équipe du Troll Penché et viens " - "d'amuser le Vendredi soir!", type="WEEKLY", club=troll, - author=subscriber, is_moderated=True, moderator=skia) + n = News( + title="Jeux sans faim", + summary="Viens jouer!", + content="Rejoins la fine équipe du Troll Penché et viens " + "d'amuser le Vendredi soir!", + type="WEEKLY", + club=troll, + author=subscriber, + is_moderated=True, + moderator=skia, + ) n.save() for i in range(10): - NewsDate(news=n, start_date=friday+timedelta(hours=24*7*i), - end_date=friday+timedelta(hours=24*7*i+8)).save() + NewsDate( + news=n, + start_date=friday + timedelta(hours=24 * 7 * i), + end_date=friday + timedelta(hours=24 * 7 * i + 8), + ).save() diff --git a/core/management/commands/repair_fs.py b/core/management/commands/repair_fs.py index 8bad0a33..055dbc9b 100644 --- a/core/management/commands/repair_fs.py +++ b/core/management/commands/repair_fs.py @@ -33,10 +33,14 @@ class Command(BaseCommand): help = "Recursively repair the file system with respect to the DB" def add_arguments(self, parser): - parser.add_argument('ids', metavar='ID', type=int, nargs='+', help="The file IDs to process") + parser.add_argument( + "ids", metavar="ID", type=int, nargs="+", help="The file IDs to process" + ) def handle(self, *args, **options): - root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - files = SithFile.objects.filter(id__in=options['ids']).all() + root_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) + files = SithFile.objects.filter(id__in=options["ids"]).all() for f in files: f._repair_fs() diff --git a/core/management/commands/setup.py b/core/management/commands/setup.py index 88be8377..128b762d 100644 --- a/core/management/commands/setup.py +++ b/core/management/commands/setup.py @@ -31,22 +31,24 @@ class Command(BaseCommand): help = "Set up a new instance of the Sith AE" def add_arguments(self, parser): - parser.add_argument('--prod', action="store_true") + parser.add_argument("--prod", action="store_true") def handle(self, *args, **options): - root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) + root_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) try: - os.mkdir(os.path.join(root_path) + '/data') + os.mkdir(os.path.join(root_path) + "/data") print("Data dir created") except Exception as e: repr(e) try: - os.remove(os.path.join(root_path, 'db.sqlite3')) + os.remove(os.path.join(root_path, "db.sqlite3")) print("db.sqlite3 deleted") except Exception as e: repr(e) - call_command('migrate') - if options['prod']: - call_command('populate', '--prod') + call_command("migrate") + if options["prod"]: + call_command("populate", "--prod") else: - call_command('populate') + call_command("populate") diff --git a/core/markdown.py b/core/markdown.py index e9ce814c..c5d69a6c 100644 --- a/core/markdown.py +++ b/core/markdown.py @@ -30,7 +30,7 @@ from django.core.urlresolvers import reverse class SithRenderer(Renderer): def file_link(self, id, suffix): - return reverse('core:file_detail', kwargs={'file_id': id}) + suffix + return reverse("core:file_detail", kwargs={"file_id": id}) + suffix def exposant(self, text): return """%s""" % text @@ -48,19 +48,19 @@ class SithRenderer(Renderer): :param text: alt text of the image. """ style = None - if '?' in original_src: - src, params = original_src.rsplit('?', maxsplit=1) - m = re.search(r'(\d+%?)(x(\d+%?))?', params) + if "?" in original_src: + src, params = original_src.rsplit("?", maxsplit=1) + m = re.search(r"(\d+%?)(x(\d+%?))?", params) if not m: src = original_src else: width = m.group(1) - if not width.endswith('%'): + if not width.endswith("%"): width += "px" style = "width: %s; " % width try: height = m.group(3) - if not height.endswith('%'): + if not height.endswith("%"): height += "px" style += "height: %s; " % height except: @@ -77,67 +77,57 @@ class SithRenderer(Renderer): html = '%s' % html - return '%s>' % html + if self.options.get("use_xhtml"): + return "%s />" % html + return "%s>" % html class SithInlineGrammar(InlineGrammar): - double_emphasis = re.compile( - r'^\*{2}([\s\S]+?)\*{2}(?!\*)' # **word** - ) - emphasis = re.compile( - r'^\*((?:\*\*|[^\*])+?)\*(?!\*)' # *word* - ) - underline = re.compile( - r'^_{2}([\s\S]+?)_{2}(?!_)' # __word__ - ) - exposant = re.compile( - r'^([\s\S]+?)' # text - ) - indice = re.compile( - r'^([\s\S]+?)' # text - ) + double_emphasis = re.compile(r"^\*{2}([\s\S]+?)\*{2}(?!\*)") # **word** + emphasis = re.compile(r"^\*((?:\*\*|[^\*])+?)\*(?!\*)") # *word* + underline = re.compile(r"^_{2}([\s\S]+?)_{2}(?!_)") # __word__ + exposant = re.compile(r"^([\s\S]+?)") # text + indice = re.compile(r"^([\s\S]+?)") # text class SithInlineLexer(InlineLexer): grammar_class = SithInlineGrammar default_rules = [ - 'escape', + "escape", # 'inline_html', - 'autolink', - 'url', - 'footnote', - 'link', - 'reflink', - 'nolink', - 'exposant', - 'double_emphasis', - 'emphasis', - 'underline', - 'indice', - 'code', - 'linebreak', - 'strikethrough', - 'text', + "autolink", + "url", + "footnote", + "link", + "reflink", + "nolink", + "exposant", + "double_emphasis", + "emphasis", + "underline", + "indice", + "code", + "linebreak", + "strikethrough", + "text", ] inline_html_rules = [ - 'escape', - 'autolink', - 'url', - 'link', - 'reflink', - 'nolink', - 'exposant', - 'double_emphasis', - 'emphasis', - 'underline', - 'indice', - 'code', - 'linebreak', - 'strikethrough', - 'text', + "escape", + "autolink", + "url", + "link", + "reflink", + "nolink", + "exposant", + "double_emphasis", + "emphasis", + "underline", + "indice", + "code", + "linebreak", + "strikethrough", + "text", ] def output_underline(self, m): @@ -166,22 +156,18 @@ class SithInlineLexer(InlineLexer): def _process_link(self, m, link, title=None): try: # Add page:// support for links - page = re.compile( - r'^page://(\S*)' # page://nom_de_ma_page - ) + page = re.compile(r"^page://(\S*)") # page://nom_de_ma_page match = page.search(link) page = match.group(1) or "" - link = reverse('core:page', kwargs={'page_name': page}) + link = reverse("core:page", kwargs={"page_name": page}) except: pass try: # Add file:// support for links - file_link = re.compile( - r'^file://(\d*)/?(\S*)?' # file://4000/download - ) + file_link = re.compile(r"^file://(\d*)/?(\S*)?") # file://4000/download match = file_link.search(link) id = match.group(1) suffix = match.group(2) or "" - link = reverse('core:file_detail', kwargs={'file_id': id}) + suffix + link = reverse("core:file_detail", kwargs={"file_id": id}) + suffix except: pass return super(SithInlineLexer, self)._process_link(m, link, title) @@ -194,6 +180,6 @@ markdown = Markdown(renderer, inline=inline) if __name__ == "__main__": root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as md: + with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md: result = markdown(md.read()) - print(result, end='') + print(result, end="") diff --git a/core/middleware.py b/core/middleware.py index 36c91bbf..37c01ff9 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -26,14 +26,16 @@ import importlib from django.conf import settings from django.utils.functional import SimpleLazyObject from django.contrib.auth import get_user -from django.contrib.auth.middleware import AuthenticationMiddleware as DjangoAuthenticationMiddleware +from django.contrib.auth.middleware import ( + AuthenticationMiddleware as DjangoAuthenticationMiddleware, +) -module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit('.', 1) +module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1) AnonymousUser = getattr(importlib.import_module(module), klass) def get_cached_user(request): - if not hasattr(request, '_cached_user'): + if not hasattr(request, "_cached_user"): user = get_user(request) if user.is_anonymous(): user = AnonymousUser(request) @@ -45,7 +47,7 @@ def get_cached_user(request): class AuthenticationMiddleware(DjangoAuthenticationMiddleware): def process_request(self, request): - assert hasattr(request, 'session'), ( + assert hasattr(request, "session"), ( "The Django authentication middleware requires session middleware " "to be installed. Edit your MIDDLEWARE_CLASSES setting to insert " "'django.contrib.sessions.middleware.SessionMiddleware' before " diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 4f611246..190ace89 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -12,169 +12,559 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('auth', '0006_require_contenttypes_0002'), - ] + dependencies = [("auth", "0006_require_contenttypes_0002")] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)), - ('username', models.CharField(help_text='Required. 254 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=254, error_messages={'unique': 'A user with that username already exists.'}, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')])), - ('first_name', models.CharField(max_length=64, verbose_name='first name')), - ('last_name', models.CharField(max_length=64, verbose_name='last name')), - ('email', models.EmailField(unique=True, max_length=254, verbose_name='email address')), - ('date_of_birth', models.DateField(null=True, verbose_name='date of birth', blank=True)), - ('nick_name', models.CharField(max_length=64, null=True, verbose_name='nick name', blank=True)), - ('is_staff', models.BooleanField(help_text='Designates whether the user can log into this admin site.', verbose_name='staff status', default=False)), - ('is_active', models.BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active', default=True)), - ('date_joined', models.DateField(auto_now_add=True, verbose_name='date joined')), - ('last_update', models.DateField(verbose_name='last update', auto_now=True)), - ('is_superuser', models.BooleanField(help_text='Designates whether this user is a superuser. ', verbose_name='superuser', default=False)), - ('sex', models.CharField(choices=[('MAN', 'Man'), ('WOMAN', 'Woman')], max_length=10, default='MAN', verbose_name='sex')), - ('tshirt_size', models.CharField(choices=[('-', '-'), ('XS', 'XS'), ('S', 'S'), ('M', 'M'), ('L', 'L'), ('XL', 'XL'), ('XXL', 'XXL'), ('XXXL', 'XXXL')], max_length=5, default='-', verbose_name='tshirt size')), - ('role', models.CharField(choices=[('STUDENT', 'Student'), ('ADMINISTRATIVE', 'Administrative agent'), ('TEACHER', 'Teacher'), ('AGENT', 'Agent'), ('DOCTOR', 'Doctor'), ('FORMER STUDENT', 'Former student'), ('SERVICE', 'Service')], max_length=15, blank=True, verbose_name='role', default='')), - ('department', models.CharField(choices=[('TC', 'TC'), ('IMSI', 'IMSI'), ('IMAP', 'IMAP'), ('INFO', 'INFO'), ('GI', 'GI'), ('E', 'E'), ('EE', 'EE'), ('GESC', 'GESC'), ('GMC', 'GMC'), ('MC', 'MC'), ('EDIM', 'EDIM'), ('HUMA', 'Humanities'), ('NA', 'N/A')], max_length=15, blank=True, verbose_name='department', default='NA')), - ('dpt_option', models.CharField(max_length=32, blank=True, verbose_name='dpt option', default='')), - ('semester', models.CharField(max_length=5, blank=True, verbose_name='semester', default='')), - ('quote', models.CharField(max_length=256, blank=True, verbose_name='quote', default='')), - ('school', models.CharField(max_length=80, blank=True, verbose_name='school', default='')), - ('promo', models.IntegerField(null=True, verbose_name='promo', validators=[core.models.validate_promo], blank=True)), - ('forum_signature', models.TextField(max_length=256, blank=True, verbose_name='forum signature', default='')), - ('second_email', models.EmailField(max_length=254, null=True, verbose_name='second email address', blank=True)), - ('phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, verbose_name='phone', blank=True)), - ('parent_phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, verbose_name='parent phone', blank=True)), - ('address', models.CharField(max_length=128, blank=True, verbose_name='address', default='')), - ('parent_address', models.CharField(max_length=128, blank=True, verbose_name='parent address', default='')), - ('is_subscriber_viewable', models.BooleanField(verbose_name='is subscriber viewable', default=True)), - ], - options={ - 'abstract': False, - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + null=True, verbose_name="last login", blank=True + ), + ), + ( + "username", + models.CharField( + help_text="Required. 254 characters or fewer. Letters, digits and @/./+/-/_ only.", + unique=True, + max_length=254, + error_messages={ + "unique": "A user with that username already exists." + }, + verbose_name="username", + validators=[ + django.core.validators.RegexValidator( + "^[\\w.@+-]+$", + "Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.", + ) + ], + ), + ), + ( + "first_name", + models.CharField(max_length=64, verbose_name="first name"), + ), + ( + "last_name", + models.CharField(max_length=64, verbose_name="last name"), + ), + ( + "email", + models.EmailField( + unique=True, max_length=254, verbose_name="email address" + ), + ), + ( + "date_of_birth", + models.DateField( + null=True, verbose_name="date of birth", blank=True + ), + ), + ( + "nick_name", + models.CharField( + max_length=64, null=True, verbose_name="nick name", blank=True + ), + ), + ( + "is_staff", + models.BooleanField( + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + default=False, + ), + ), + ( + "is_active", + models.BooleanField( + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + default=True, + ), + ), + ( + "date_joined", + models.DateField(auto_now_add=True, verbose_name="date joined"), + ), + ( + "last_update", + models.DateField(verbose_name="last update", auto_now=True), + ), + ( + "is_superuser", + models.BooleanField( + help_text="Designates whether this user is a superuser. ", + verbose_name="superuser", + default=False, + ), + ), + ( + "sex", + models.CharField( + choices=[("MAN", "Man"), ("WOMAN", "Woman")], + max_length=10, + default="MAN", + verbose_name="sex", + ), + ), + ( + "tshirt_size", + models.CharField( + choices=[ + ("-", "-"), + ("XS", "XS"), + ("S", "S"), + ("M", "M"), + ("L", "L"), + ("XL", "XL"), + ("XXL", "XXL"), + ("XXXL", "XXXL"), + ], + max_length=5, + default="-", + verbose_name="tshirt size", + ), + ), + ( + "role", + models.CharField( + choices=[ + ("STUDENT", "Student"), + ("ADMINISTRATIVE", "Administrative agent"), + ("TEACHER", "Teacher"), + ("AGENT", "Agent"), + ("DOCTOR", "Doctor"), + ("FORMER STUDENT", "Former student"), + ("SERVICE", "Service"), + ], + max_length=15, + blank=True, + verbose_name="role", + default="", + ), + ), + ( + "department", + models.CharField( + choices=[ + ("TC", "TC"), + ("IMSI", "IMSI"), + ("IMAP", "IMAP"), + ("INFO", "INFO"), + ("GI", "GI"), + ("E", "E"), + ("EE", "EE"), + ("GESC", "GESC"), + ("GMC", "GMC"), + ("MC", "MC"), + ("EDIM", "EDIM"), + ("HUMA", "Humanities"), + ("NA", "N/A"), + ], + max_length=15, + blank=True, + verbose_name="department", + default="NA", + ), + ), + ( + "dpt_option", + models.CharField( + max_length=32, blank=True, verbose_name="dpt option", default="" + ), + ), + ( + "semester", + models.CharField( + max_length=5, blank=True, verbose_name="semester", default="" + ), + ), + ( + "quote", + models.CharField( + max_length=256, blank=True, verbose_name="quote", default="" + ), + ), + ( + "school", + models.CharField( + max_length=80, blank=True, verbose_name="school", default="" + ), + ), + ( + "promo", + models.IntegerField( + null=True, + verbose_name="promo", + validators=[core.models.validate_promo], + blank=True, + ), + ), + ( + "forum_signature", + models.TextField( + max_length=256, + blank=True, + verbose_name="forum signature", + default="", + ), + ), + ( + "second_email", + models.EmailField( + max_length=254, + null=True, + verbose_name="second email address", + blank=True, + ), + ), + ( + "phone", + phonenumber_field.modelfields.PhoneNumberField( + max_length=128, null=True, verbose_name="phone", blank=True + ), + ), + ( + "parent_phone", + phonenumber_field.modelfields.PhoneNumberField( + max_length=128, + null=True, + verbose_name="parent phone", + blank=True, + ), + ), + ( + "address", + models.CharField( + max_length=128, blank=True, verbose_name="address", default="" + ), + ), + ( + "parent_address", + models.CharField( + max_length=128, + blank=True, + verbose_name="parent address", + default="", + ), + ), + ( + "is_subscriber_viewable", + models.BooleanField( + verbose_name="is subscriber viewable", default=True + ), + ), ], + options={"abstract": False}, + managers=[("objects", django.contrib.auth.models.UserManager())], ), migrations.CreateModel( - name='Group', + name="Group", fields=[ - ('group_ptr', models.OneToOneField(primary_key=True, parent_link=True, serialize=False, to='auth.Group', auto_created=True)), - ('is_meta', models.BooleanField(help_text='Whether a group is a meta group or not', verbose_name='meta group status', default=False)), - ('description', models.CharField(max_length=60, verbose_name='description')), + ( + "group_ptr", + models.OneToOneField( + primary_key=True, + parent_link=True, + serialize=False, + to="auth.Group", + auto_created=True, + ), + ), + ( + "is_meta", + models.BooleanField( + help_text="Whether a group is a meta group or not", + verbose_name="meta group status", + default=False, + ), + ), + ( + "description", + models.CharField(max_length=60, verbose_name="description"), + ), ], - bases=('auth.group',), + bases=("auth.group",), ), migrations.CreateModel( - name='Page', + name="Page", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='page name')), - ('_full_name', models.CharField(max_length=255, blank=True, verbose_name='page name')), - ('edit_groups', models.ManyToManyField(related_name='editable_page', to='core.Group', blank=True, verbose_name='edit group')), - ('owner_group', models.ForeignKey(default=1, related_name='owned_page', verbose_name='owner group', to='core.Group')), - ('parent', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='children', verbose_name='parent', to='core.Page', blank=True)), - ('view_groups', models.ManyToManyField(related_name='viewable_page', to='core.Group', blank=True, verbose_name='view group')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="page name")), + ( + "_full_name", + models.CharField( + max_length=255, blank=True, verbose_name="page name" + ), + ), + ( + "edit_groups", + models.ManyToManyField( + related_name="editable_page", + to="core.Group", + blank=True, + verbose_name="edit group", + ), + ), + ( + "owner_group", + models.ForeignKey( + default=1, + related_name="owned_page", + verbose_name="owner group", + to="core.Group", + ), + ), + ( + "parent", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="children", + verbose_name="parent", + to="core.Page", + blank=True, + ), + ), + ( + "view_groups", + models.ManyToManyField( + related_name="viewable_page", + to="core.Group", + blank=True, + verbose_name="view group", + ), + ), ], options={ - 'permissions': (('change_prop_page', "Can change the page's properties (groups, ...)"), ('view_page', 'Can view the page')), + "permissions": ( + ( + "change_prop_page", + "Can change the page's properties (groups, ...)", + ), + ("view_page", "Can view the page"), + ) }, ), migrations.CreateModel( - name='PageRev', + name="PageRev", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('revision', models.IntegerField(verbose_name='revision')), - ('title', models.CharField(max_length=255, blank=True, verbose_name='page title')), - ('content', models.TextField(blank=True, verbose_name='page content')), - ('date', models.DateTimeField(verbose_name='date', auto_now=True)), - ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='page_rev')), - ('page', models.ForeignKey(to='core.Page', related_name='revisions')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("revision", models.IntegerField(verbose_name="revision")), + ( + "title", + models.CharField( + max_length=255, blank=True, verbose_name="page title" + ), + ), + ("content", models.TextField(blank=True, verbose_name="page content")), + ("date", models.DateTimeField(verbose_name="date", auto_now=True)), + ( + "author", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, related_name="page_rev" + ), + ), + ("page", models.ForeignKey(to="core.Page", related_name="revisions")), ], - options={ - 'ordering': ['date'], - }, + options={"ordering": ["date"]}, ), migrations.CreateModel( - name='Preferences', + name="Preferences", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('show_my_stats', models.BooleanField(help_text='Show your account statistics to others', verbose_name='define if we show a users stats', default=False)), - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='preferences')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "show_my_stats", + models.BooleanField( + help_text="Show your account statistics to others", + verbose_name="define if we show a users stats", + default=False, + ), + ), + ( + "user", + models.OneToOneField( + to=settings.AUTH_USER_MODEL, related_name="preferences" + ), + ), ], ), migrations.CreateModel( - name='SithFile', + name="SithFile", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='file name')), - ('file', models.FileField(upload_to=core.models.get_directory, null=True, verbose_name='file', blank=True)), - ('is_folder', models.BooleanField(verbose_name='is folder', default=True)), - ('mime_type', models.CharField(max_length=30, verbose_name='mime type')), - ('size', models.IntegerField(default=0, verbose_name='size')), - ('date', models.DateTimeField(verbose_name='date', auto_now=True)), - ('edit_groups', models.ManyToManyField(related_name='editable_files', to='core.Group', blank=True, verbose_name='edit group')), - ('owner', models.ForeignKey(verbose_name='owner', to=settings.AUTH_USER_MODEL, related_name='owned_files')), - ('parent', models.ForeignKey(null=True, related_name='children', verbose_name='parent', to='core.SithFile', blank=True)), - ('view_groups', models.ManyToManyField(related_name='viewable_files', to='core.Group', blank=True, verbose_name='view group')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="file name")), + ( + "file", + models.FileField( + upload_to=core.models.get_directory, + null=True, + verbose_name="file", + blank=True, + ), + ), + ( + "is_folder", + models.BooleanField(verbose_name="is folder", default=True), + ), + ( + "mime_type", + models.CharField(max_length=30, verbose_name="mime type"), + ), + ("size", models.IntegerField(default=0, verbose_name="size")), + ("date", models.DateTimeField(verbose_name="date", auto_now=True)), + ( + "edit_groups", + models.ManyToManyField( + related_name="editable_files", + to="core.Group", + blank=True, + verbose_name="edit group", + ), + ), + ( + "owner", + models.ForeignKey( + verbose_name="owner", + to=settings.AUTH_USER_MODEL, + related_name="owned_files", + ), + ), + ( + "parent", + models.ForeignKey( + null=True, + related_name="children", + verbose_name="parent", + to="core.SithFile", + blank=True, + ), + ), + ( + "view_groups", + models.ManyToManyField( + related_name="viewable_files", + to="core.Group", + blank=True, + verbose_name="view group", + ), + ), ], - options={ - 'verbose_name': 'file', - }, + options={"verbose_name": "file"}, ), migrations.AddField( - model_name='user', - name='avatar_pict', - field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='avatar_of', verbose_name='avatar', to='core.SithFile'), + model_name="user", + name="avatar_pict", + field=models.OneToOneField( + blank=True, + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="avatar_of", + verbose_name="avatar", + to="core.SithFile", + ), ), migrations.AddField( - model_name='user', - name='home', - field=models.OneToOneField(blank=True, null=True, related_name='home_of', verbose_name='home', to='core.SithFile'), + model_name="user", + name="home", + field=models.OneToOneField( + blank=True, + null=True, + related_name="home_of", + verbose_name="home", + to="core.SithFile", + ), ), migrations.AddField( - model_name='user', - name='profile_pict', - field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='profile_of', verbose_name='profile', to='core.SithFile'), + model_name="user", + name="profile_pict", + field=models.OneToOneField( + blank=True, + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="profile_of", + verbose_name="profile", + to="core.SithFile", + ), ), migrations.AddField( - model_name='user', - name='scrub_pict', - field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='scrub_of', verbose_name='scrub', to='core.SithFile'), + model_name="user", + name="scrub_pict", + field=models.OneToOneField( + blank=True, + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="scrub_of", + verbose_name="scrub", + to="core.SithFile", + ), ), migrations.CreateModel( - name='MetaGroup', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('core.group',), - managers=[ - ('objects', core.models.MetaGroupManager()), - ], + name="MetaGroup", + fields=[], + options={"proxy": True}, + bases=("core.group",), + managers=[("objects", core.models.MetaGroupManager())], ), migrations.CreateModel( - name='RealGroup', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('core.group',), - managers=[ - ('objects', core.models.RealGroupManager()), - ], + name="RealGroup", + fields=[], + options={"proxy": True}, + bases=("core.group",), + managers=[("objects", core.models.RealGroupManager())], ), migrations.AlterUniqueTogether( - name='page', - unique_together=set([('name', 'parent')]), + name="page", unique_together=set([("name", "parent")]) ), migrations.AddField( - model_name='user', - name='groups', - field=models.ManyToManyField(to='core.RealGroup', blank=True, related_name='users'), + model_name="user", + name="groups", + field=models.ManyToManyField( + to="core.RealGroup", blank=True, related_name="users" + ), ), ] diff --git a/core/migrations/0002_auto_20160831_0144.py b/core/migrations/0002_auto_20160831_0144.py index a2b49e2b..cc5fa3f1 100644 --- a/core/migrations/0002_auto_20160831_0144.py +++ b/core/migrations/0002_auto_20160831_0144.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0001_initial'), - ] + dependencies = [("core", "0001_initial")] operations = [ migrations.AlterField( - model_name='sithfile', - name='name', - field=models.CharField(verbose_name='file name', max_length=256), - ), + model_name="sithfile", + name="name", + field=models.CharField(verbose_name="file name", max_length=256), + ) ] diff --git a/core/migrations/0003_auto_20160902_1914.py b/core/migrations/0003_auto_20160902_1914.py index f5d2c28b..fa6274c4 100644 --- a/core/migrations/0003_auto_20160902_1914.py +++ b/core/migrations/0003_auto_20160902_1914.py @@ -7,14 +7,24 @@ import django.core.validators class Migration(migrations.Migration): - dependencies = [ - ('core', '0002_auto_20160831_0144'), - ] + dependencies = [("core", "0002_auto_20160831_0144")] operations = [ migrations.AlterField( - model_name='user', - name='username', - field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=254, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.+-]+$', 'Enter a valid username. This value may contain only letters, numbers and ./+/-/_ characters.')], help_text='Required. 254 characters or fewer. Letters, digits and ./+/-/_ only.', verbose_name='username'), - ), + model_name="user", + name="username", + field=models.CharField( + error_messages={"unique": "A user with that username already exists."}, + max_length=254, + unique=True, + validators=[ + django.core.validators.RegexValidator( + "^[\\w.+-]+$", + "Enter a valid username. This value may contain only letters, numbers and ./+/-/_ characters.", + ) + ], + help_text="Required. 254 characters or fewer. Letters, digits and ./+/-/_ only.", + verbose_name="username", + ), + ) ] diff --git a/core/migrations/0004_user_godfathers.py b/core/migrations/0004_user_godfathers.py index 3bb1f8e6..e3e33d2c 100644 --- a/core/migrations/0004_user_godfathers.py +++ b/core/migrations/0004_user_godfathers.py @@ -7,14 +7,14 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('core', '0003_auto_20160902_1914'), - ] + dependencies = [("core", "0003_auto_20160902_1914")] operations = [ migrations.AddField( - model_name='user', - name='godfathers', - field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, related_name='godchildren', blank=True), - ), + model_name="user", + name="godfathers", + field=models.ManyToManyField( + to=settings.AUTH_USER_MODEL, related_name="godchildren", blank=True + ), + ) ] diff --git a/core/migrations/0005_auto_20161105_1035.py b/core/migrations/0005_auto_20161105_1035.py index da6241d4..803b373a 100644 --- a/core/migrations/0005_auto_20161105_1035.py +++ b/core/migrations/0005_auto_20161105_1035.py @@ -7,19 +7,26 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('core', '0004_user_godfathers'), - ] + dependencies = [("core", "0004_user_godfathers")] operations = [ migrations.AddField( - model_name='page', - name='lock_timeout', - field=models.DateTimeField(verbose_name='lock_timeout', null=True, blank=True, default=None), + model_name="page", + name="lock_timeout", + field=models.DateTimeField( + verbose_name="lock_timeout", null=True, blank=True, default=None + ), ), migrations.AddField( - model_name='page', - name='lock_user', - field=models.ForeignKey(verbose_name='lock user', default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True, related_name='locked_pages'), + model_name="page", + name="lock_user", + field=models.ForeignKey( + verbose_name="lock user", + default=None, + blank=True, + to=settings.AUTH_USER_MODEL, + null=True, + related_name="locked_pages", + ), ), ] diff --git a/core/migrations/0006_auto_20161108_1703.py b/core/migrations/0006_auto_20161108_1703.py index e9c2a04d..21b5a969 100644 --- a/core/migrations/0006_auto_20161108_1703.py +++ b/core/migrations/0006_auto_20161108_1703.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0005_auto_20161105_1035'), - ] + dependencies = [("core", "0005_auto_20161105_1035")] operations = [ migrations.AddField( - model_name='sithfile', - name='is_moderated', - field=models.BooleanField(verbose_name='is moderated', default=False), - ), + model_name="sithfile", + name="is_moderated", + field=models.BooleanField(verbose_name="is moderated", default=False), + ) ] diff --git a/core/migrations/0008_sithfile_asked_for_removal.py b/core/migrations/0008_sithfile_asked_for_removal.py index 79ae887b..db3bc23d 100644 --- a/core/migrations/0008_sithfile_asked_for_removal.py +++ b/core/migrations/0008_sithfile_asked_for_removal.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0006_auto_20161108_1703'), - ] + dependencies = [("core", "0006_auto_20161108_1703")] operations = [ migrations.AddField( - model_name='sithfile', - name='asked_for_removal', - field=models.BooleanField(default=False, verbose_name='asked for removal'), - ), + model_name="sithfile", + name="asked_for_removal", + field=models.BooleanField(default=False, verbose_name="asked for removal"), + ) ] diff --git a/core/migrations/0009_auto_20161120_1155.py b/core/migrations/0009_auto_20161120_1155.py index 524e29da..b2fccdf4 100644 --- a/core/migrations/0009_auto_20161120_1155.py +++ b/core/migrations/0009_auto_20161120_1155.py @@ -8,24 +8,39 @@ import core.models class Migration(migrations.Migration): - dependencies = [ - ('core', '0008_sithfile_asked_for_removal'), - ] + dependencies = [("core", "0008_sithfile_asked_for_removal")] operations = [ migrations.AddField( - model_name='sithfile', - name='compressed', - field=models.FileField(upload_to=core.models.get_compressed_directory, null=True, verbose_name='compressed file', blank=True), + model_name="sithfile", + name="compressed", + field=models.FileField( + upload_to=core.models.get_compressed_directory, + null=True, + verbose_name="compressed file", + blank=True, + ), ), migrations.AddField( - model_name='sithfile', - name='thumbnail', - field=models.FileField(upload_to=core.models.get_thumbnail_directory, null=True, verbose_name='thumbnail', blank=True), + model_name="sithfile", + name="thumbnail", + field=models.FileField( + upload_to=core.models.get_thumbnail_directory, + null=True, + verbose_name="thumbnail", + blank=True, + ), ), migrations.AlterField( - model_name='user', - name='home', - field=models.OneToOneField(verbose_name='home', related_name='home_of', on_delete=django.db.models.deletion.SET_NULL, null=True, to='core.SithFile', blank=True), + model_name="user", + name="home", + field=models.OneToOneField( + verbose_name="home", + related_name="home_of", + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to="core.SithFile", + blank=True, + ), ), ] diff --git a/core/migrations/0010_sithfile_is_in_sas.py b/core/migrations/0010_sithfile_is_in_sas.py index 2a3237be..38664c6f 100644 --- a/core/migrations/0010_sithfile_is_in_sas.py +++ b/core/migrations/0010_sithfile_is_in_sas.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0009_auto_20161120_1155'), - ] + dependencies = [("core", "0009_auto_20161120_1155")] operations = [ migrations.AddField( - model_name='sithfile', - name='is_in_sas', - field=models.BooleanField(verbose_name='is in the SAS', default=False), - ), + model_name="sithfile", + name="is_in_sas", + field=models.BooleanField(verbose_name="is in the SAS", default=False), + ) ] diff --git a/core/migrations/0011_auto_20161124_0848.py b/core/migrations/0011_auto_20161124_0848.py index 8baba108..a972bff6 100644 --- a/core/migrations/0011_auto_20161124_0848.py +++ b/core/migrations/0011_auto_20161124_0848.py @@ -8,29 +8,47 @@ import core.models class Migration(migrations.Migration): - dependencies = [ - ('core', '0010_sithfile_is_in_sas'), - ] + dependencies = [("core", "0010_sithfile_is_in_sas")] operations = [ migrations.AlterField( - model_name='sithfile', - name='compressed', - field=models.FileField(verbose_name='compressed file', upload_to=core.models.get_compressed_directory, null=True, blank=True, max_length=256), + model_name="sithfile", + name="compressed", + field=models.FileField( + verbose_name="compressed file", + upload_to=core.models.get_compressed_directory, + null=True, + blank=True, + max_length=256, + ), ), migrations.AlterField( - model_name='sithfile', - name='date', - field=models.DateTimeField(verbose_name='date', default=django.utils.timezone.now), + model_name="sithfile", + name="date", + field=models.DateTimeField( + verbose_name="date", default=django.utils.timezone.now + ), ), migrations.AlterField( - model_name='sithfile', - name='file', - field=models.FileField(verbose_name='file', upload_to=core.models.get_directory, null=True, blank=True, max_length=256), + model_name="sithfile", + name="file", + field=models.FileField( + verbose_name="file", + upload_to=core.models.get_directory, + null=True, + blank=True, + max_length=256, + ), ), migrations.AlterField( - model_name='sithfile', - name='thumbnail', - field=models.FileField(verbose_name='thumbnail', upload_to=core.models.get_thumbnail_directory, null=True, blank=True, max_length=256), + model_name="sithfile", + name="thumbnail", + field=models.FileField( + verbose_name="thumbnail", + upload_to=core.models.get_thumbnail_directory, + null=True, + blank=True, + max_length=256, + ), ), ] diff --git a/core/migrations/0012_notification.py b/core/migrations/0012_notification.py index 01a9308b..f48e4063 100644 --- a/core/migrations/0012_notification.py +++ b/core/migrations/0012_notification.py @@ -8,20 +8,49 @@ import django.utils.timezone class Migration(migrations.Migration): - dependencies = [ - ('core', '0011_auto_20161124_0848'), - ] + dependencies = [("core", "0011_auto_20161124_0848")] operations = [ migrations.CreateModel( - name='Notification', + name="Notification", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('url', models.CharField(max_length=255, verbose_name='url')), - ('text', models.CharField(max_length=512, verbose_name='text')), - ('type', models.CharField(max_length=16, choices=[('FILE_MODERATION', 'File moderation'), ('SAS_MODERATION', 'SAS moderation'), ('NEW_PICTURES', 'New pictures')], verbose_name='text', null=True, blank=True)), - ('date', models.DateTimeField(verbose_name='date', default=django.utils.timezone.now)), - ('user', models.ForeignKey(related_name='notifications', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + auto_created=True, + serialize=False, + ), + ), + ("url", models.CharField(max_length=255, verbose_name="url")), + ("text", models.CharField(max_length=512, verbose_name="text")), + ( + "type", + models.CharField( + max_length=16, + choices=[ + ("FILE_MODERATION", "File moderation"), + ("SAS_MODERATION", "SAS moderation"), + ("NEW_PICTURES", "New pictures"), + ], + verbose_name="text", + null=True, + blank=True, + ), + ), + ( + "date", + models.DateTimeField( + verbose_name="date", default=django.utils.timezone.now + ), + ), + ( + "user", + models.ForeignKey( + related_name="notifications", to=settings.AUTH_USER_MODEL + ), + ), ], - ), + ) ] diff --git a/core/migrations/0013_auto_20161209_2338.py b/core/migrations/0013_auto_20161209_2338.py index e0c6cff8..e0475c5c 100644 --- a/core/migrations/0013_auto_20161209_2338.py +++ b/core/migrations/0013_auto_20161209_2338.py @@ -6,23 +6,18 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0012_notification'), - ] + dependencies = [("core", "0012_notification")] operations = [ - migrations.RemoveField( - model_name='notification', - name='text', + migrations.RemoveField(model_name="notification", name="text"), + migrations.AddField( + model_name="notification", + name="param", + field=models.CharField(verbose_name="param", default="", max_length=128), ), migrations.AddField( - model_name='notification', - name='param', - field=models.CharField(verbose_name='param', default='', max_length=128), - ), - migrations.AddField( - model_name='notification', - name='viewed', - field=models.BooleanField(verbose_name='viewed', default=False), + model_name="notification", + name="viewed", + field=models.BooleanField(verbose_name="viewed", default=False), ), ] diff --git a/core/migrations/0014_auto_20161210_0009.py b/core/migrations/0014_auto_20161210_0009.py index c6f8db7b..0c17aec7 100644 --- a/core/migrations/0014_auto_20161210_0009.py +++ b/core/migrations/0014_auto_20161210_0009.py @@ -6,14 +6,24 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0013_auto_20161209_2338'), - ] + dependencies = [("core", "0013_auto_20161209_2338")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(verbose_name='type', max_length=32, default='GENERIC', choices=[('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')]), - ), + model_name="notification", + name="type", + field=models.CharField( + verbose_name="type", + max_length=32, + default="GENERIC", + choices=[ + ("FILE_MODERATION", "New files to be moderated"), + ("SAS_MODERATION", "New pictures/album to be moderated in the SAS"), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + ), + ) ] diff --git a/core/migrations/0015_sithfile_moderator.py b/core/migrations/0015_sithfile_moderator.py index 753c82f7..00e2f1f9 100644 --- a/core/migrations/0015_sithfile_moderator.py +++ b/core/migrations/0015_sithfile_moderator.py @@ -7,15 +7,18 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('core', '0014_auto_20161210_0009'), - ] + dependencies = [("core", "0014_auto_20161210_0009")] operations = [ migrations.AddField( - model_name='sithfile', - name='moderator', - field=models.ForeignKey(related_name='moderated_files', verbose_name='owner', default=0, to=settings.AUTH_USER_MODEL), + model_name="sithfile", + name="moderator", + field=models.ForeignKey( + related_name="moderated_files", + verbose_name="owner", + default=0, + to=settings.AUTH_USER_MODEL, + ), preserve_default=False, - ), + ) ] diff --git a/core/migrations/0016_auto_20161212_1922.py b/core/migrations/0016_auto_20161212_1922.py index 6785eb32..e98f06d6 100644 --- a/core/migrations/0016_auto_20161212_1922.py +++ b/core/migrations/0016_auto_20161212_1922.py @@ -7,14 +7,18 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('core', '0015_sithfile_moderator'), - ] + dependencies = [("core", "0015_sithfile_moderator")] operations = [ migrations.AlterField( - model_name='sithfile', - name='moderator', - field=models.ForeignKey(related_name='moderated_files', blank=True, null=True, to=settings.AUTH_USER_MODEL, verbose_name='owner'), - ), + model_name="sithfile", + name="moderator", + field=models.ForeignKey( + related_name="moderated_files", + blank=True, + null=True, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ) ] diff --git a/core/migrations/0017_auto_20161220_1626.py b/core/migrations/0017_auto_20161220_1626.py index 89e9bd3a..51af77e5 100644 --- a/core/migrations/0017_auto_20161220_1626.py +++ b/core/migrations/0017_auto_20161220_1626.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0016_auto_20161212_1922'), - ] + dependencies = [("core", "0016_auto_20161212_1922")] operations = [ migrations.AlterField( - model_name='user', - name='last_update', - field=models.DateTimeField(verbose_name='last update', auto_now=True), - ), + model_name="user", + name="last_update", + field=models.DateTimeField(verbose_name="last update", auto_now=True), + ) ] diff --git a/core/migrations/0018_auto_20161224_0211.py b/core/migrations/0018_auto_20161224_0211.py index ed5db107..a3daf6f6 100644 --- a/core/migrations/0018_auto_20161224_0211.py +++ b/core/migrations/0018_auto_20161224_0211.py @@ -6,14 +6,25 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0017_auto_20161220_1626'), - ] + dependencies = [("core", "0017_auto_20161220_1626")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(choices=[('NEWS_MODERATION', 'A fresh new to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], default='GENERIC', max_length=32, verbose_name='type'), - ), + model_name="notification", + name="type", + field=models.CharField( + choices=[ + ("NEWS_MODERATION", "A fresh new to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ("SAS_MODERATION", "New pictures/album to be moderated in the SAS"), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + default="GENERIC", + max_length=32, + verbose_name="type", + ), + ) ] diff --git a/core/migrations/0019_preferences_receive_weekmail.py b/core/migrations/0019_preferences_receive_weekmail.py index 7f7b117a..951488aa 100644 --- a/core/migrations/0019_preferences_receive_weekmail.py +++ b/core/migrations/0019_preferences_receive_weekmail.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0018_auto_20161224_0211'), - ] + dependencies = [("core", "0018_auto_20161224_0211")] operations = [ migrations.AddField( - model_name='preferences', - name='receive_weekmail', - field=models.BooleanField(default=False, verbose_name='do you want to receive the weekmail'), - ), + model_name="preferences", + name="receive_weekmail", + field=models.BooleanField( + default=False, verbose_name="do you want to receive the weekmail" + ), + ) ] diff --git a/core/migrations/0020_auto_20170324_0917.py b/core/migrations/0020_auto_20170324_0917.py index 8867e399..09b390f3 100644 --- a/core/migrations/0020_auto_20170324_0917.py +++ b/core/migrations/0020_auto_20170324_0917.py @@ -7,18 +7,22 @@ import django.core.validators class Migration(migrations.Migration): - dependencies = [ - ('core', '0019_preferences_receive_weekmail'), - ] + dependencies = [("core", "0019_preferences_receive_weekmail")] operations = [ - migrations.AlterModelOptions( - name='group', - options={'ordering': ['name']}, - ), + migrations.AlterModelOptions(name="group", options={"ordering": ["name"]}), migrations.AlterField( - model_name='page', - name='name', - field=models.CharField(validators=[django.core.validators.RegexValidator('^[A-z.+-]+$', 'Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.')], max_length=30, verbose_name='page unix name'), + model_name="page", + name="name", + field=models.CharField( + validators=[ + django.core.validators.RegexValidator( + "^[A-z.+-]+$", + "Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.", + ) + ], + max_length=30, + verbose_name="page unix name", + ), ), ] diff --git a/core/migrations/0021_auto_20170822_1529.py b/core/migrations/0021_auto_20170822_1529.py index b38e737d..105d280c 100644 --- a/core/migrations/0021_auto_20170822_1529.py +++ b/core/migrations/0021_auto_20170822_1529.py @@ -6,14 +6,26 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0020_auto_20170324_0917'), - ] + dependencies = [("core", "0020_auto_20170324_0917")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(verbose_name='type', default='GENERIC', max_length=32, choices=[('MAILING_MODERATION', 'A new mailing list neet to be moderated'), ('NEWS_MODERATION', 'A fresh new to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')]), - ), + model_name="notification", + name="type", + field=models.CharField( + verbose_name="type", + default="GENERIC", + max_length=32, + choices=[ + ("MAILING_MODERATION", "A new mailing list neet to be moderated"), + ("NEWS_MODERATION", "A fresh new to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ("SAS_MODERATION", "New pictures/album to be moderated in the SAS"), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + ), + ) ] diff --git a/core/migrations/0022_auto_20170822_2232.py b/core/migrations/0022_auto_20170822_2232.py index 111ea439..787efb81 100644 --- a/core/migrations/0022_auto_20170822_2232.py +++ b/core/migrations/0022_auto_20170822_2232.py @@ -6,14 +6,26 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0021_auto_20170822_1529'), - ] + dependencies = [("core", "0021_auto_20170822_1529")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(choices=[('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'A fresh new to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], default='GENERIC', max_length=32, verbose_name='type'), - ), + model_name="notification", + name="type", + field=models.CharField( + choices=[ + ("MAILING_MODERATION", "A new mailing list needs to be moderated"), + ("NEWS_MODERATION", "A fresh new to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ("SAS_MODERATION", "New pictures/album to be moderated in the SAS"), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + default="GENERIC", + max_length=32, + verbose_name="type", + ), + ) ] diff --git a/core/migrations/0023_auto_20170902_1226.py b/core/migrations/0023_auto_20170902_1226.py index 0398f09e..f932cba9 100644 --- a/core/migrations/0023_auto_20170902_1226.py +++ b/core/migrations/0023_auto_20170902_1226.py @@ -7,29 +7,35 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('core', '0022_auto_20170822_2232'), - ] + dependencies = [("core", "0022_auto_20170822_2232")] operations = [ migrations.AddField( - model_name='preferences', - name='notify_on_click', - field=models.BooleanField(verbose_name='get a notification for every click', default=False), + model_name="preferences", + name="notify_on_click", + field=models.BooleanField( + verbose_name="get a notification for every click", default=False + ), ), migrations.AddField( - model_name='preferences', - name='notify_on_refill', - field=models.BooleanField(verbose_name='get a notification for every refilling', default=False), + model_name="preferences", + name="notify_on_refill", + field=models.BooleanField( + verbose_name="get a notification for every refilling", default=False + ), ), migrations.AlterField( - model_name='preferences', - name='show_my_stats', - field=models.BooleanField(verbose_name='show your stats to others', default=False), + model_name="preferences", + name="show_my_stats", + field=models.BooleanField( + verbose_name="show your stats to others", default=False + ), ), migrations.AlterField( - model_name='preferences', - name='user', - field=models.OneToOneField(related_name='_preferences', to=settings.AUTH_USER_MODEL), + model_name="preferences", + name="user", + field=models.OneToOneField( + related_name="_preferences", to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/core/migrations/0024_auto_20170906_1317.py b/core/migrations/0024_auto_20170906_1317.py index a226d268..1bd51690 100644 --- a/core/migrations/0024_auto_20170906_1317.py +++ b/core/migrations/0024_auto_20170906_1317.py @@ -6,14 +6,26 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0023_auto_20170902_1226'), - ] + dependencies = [("core", "0023_auto_20170902_1226")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(choices=[('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'There are %s fresh news to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], verbose_name='type', default='GENERIC', max_length=32), - ), + model_name="notification", + name="type", + field=models.CharField( + choices=[ + ("MAILING_MODERATION", "A new mailing list needs to be moderated"), + ("NEWS_MODERATION", "There are %s fresh news to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ("SAS_MODERATION", "New pictures/album to be moderated in the SAS"), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + verbose_name="type", + default="GENERIC", + max_length=32, + ), + ) ] diff --git a/core/migrations/0025_auto_20170919_1521.py b/core/migrations/0025_auto_20170919_1521.py index 039f9dd2..36370264 100644 --- a/core/migrations/0025_auto_20170919_1521.py +++ b/core/migrations/0025_auto_20170919_1521.py @@ -7,14 +7,21 @@ import django.core.validators class Migration(migrations.Migration): - dependencies = [ - ('core', '0024_auto_20170906_1317'), - ] + dependencies = [("core", "0024_auto_20170906_1317")] operations = [ migrations.AlterField( - model_name='page', - name='name', - field=models.CharField(max_length=30, verbose_name='page unix name', validators=[django.core.validators.RegexValidator('^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$', 'Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.')]), - ), + model_name="page", + name="name", + field=models.CharField( + max_length=30, + verbose_name="page unix name", + validators=[ + django.core.validators.RegexValidator( + "^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$", + "Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.", + ) + ], + ), + ) ] diff --git a/core/migrations/0026_auto_20170926_1512.py b/core/migrations/0026_auto_20170926_1512.py index 03e90c4b..02ddbad5 100644 --- a/core/migrations/0026_auto_20170926_1512.py +++ b/core/migrations/0026_auto_20170926_1512.py @@ -6,14 +6,29 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0025_auto_20170919_1521'), - ] + dependencies = [("core", "0025_auto_20170919_1521")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(choices=[('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'There are %s fresh news to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'There are %s pictures to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], verbose_name='type', max_length=32, default='GENERIC'), - ), + model_name="notification", + name="type", + field=models.CharField( + choices=[ + ("MAILING_MODERATION", "A new mailing list needs to be moderated"), + ("NEWS_MODERATION", "There are %s fresh news to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ( + "SAS_MODERATION", + "There are %s pictures to be moderated in the SAS", + ), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + verbose_name="type", + max_length=32, + default="GENERIC", + ), + ) ] diff --git a/core/migrations/0027_gift.py b/core/migrations/0027_gift.py index a6c604e8..bfac2175 100644 --- a/core/migrations/0027_gift.py +++ b/core/migrations/0027_gift.py @@ -8,18 +8,34 @@ import django.utils.timezone class Migration(migrations.Migration): - dependencies = [ - ('core', '0026_auto_20170926_1512'), - ] + dependencies = [("core", "0026_auto_20170926_1512")] operations = [ migrations.CreateModel( - name='Gift', + name="Gift", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)), - ('label', models.CharField(max_length=255, verbose_name='label')), - ('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date')), - ('user', models.ForeignKey(related_name='gifts', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + primary_key=True, + auto_created=True, + verbose_name="ID", + serialize=False, + ), + ), + ("label", models.CharField(max_length=255, verbose_name="label")), + ( + "date", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date" + ), + ), + ( + "user", + models.ForeignKey( + related_name="gifts", to=settings.AUTH_USER_MODEL + ), + ), ], - ), + ) ] diff --git a/core/migrations/0028_auto_20171216_2044.py b/core/migrations/0028_auto_20171216_2044.py index 28403d6b..f54df0e2 100644 --- a/core/migrations/0028_auto_20171216_2044.py +++ b/core/migrations/0028_auto_20171216_2044.py @@ -6,14 +6,30 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0027_gift'), - ] + dependencies = [("core", "0027_gift")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(default='GENERIC', verbose_name='type', max_length=32, choices=[('POSTER_MODERATION', 'A new poster needs to be moderated'), ('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'There are %s fresh news to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'There are %s pictures to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')]), - ), + model_name="notification", + name="type", + field=models.CharField( + default="GENERIC", + verbose_name="type", + max_length=32, + choices=[ + ("POSTER_MODERATION", "A new poster needs to be moderated"), + ("MAILING_MODERATION", "A new mailing list needs to be moderated"), + ("NEWS_MODERATION", "There are %s fresh news to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ( + "SAS_MODERATION", + "There are %s pictures to be moderated in the SAS", + ), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + ), + ) ] diff --git a/core/migrations/0029_auto_20180426_2013.py b/core/migrations/0029_auto_20180426_2013.py index fd783652..4f80245d 100644 --- a/core/migrations/0029_auto_20180426_2013.py +++ b/core/migrations/0029_auto_20180426_2013.py @@ -7,14 +7,17 @@ import core.models class Migration(migrations.Migration): - dependencies = [ - ('core', '0028_auto_20171216_2044'), - ] + dependencies = [("core", "0028_auto_20171216_2044")] operations = [ migrations.AlterField( - model_name='page', - name='owner_group', - field=models.ForeignKey(verbose_name='owner group', default=core.models.Page.get_default_owner_group, related_name='owned_page', to='core.Group'), - ), + model_name="page", + name="owner_group", + field=models.ForeignKey( + verbose_name="owner group", + default=core.models.Page.get_default_owner_group, + related_name="owned_page", + to="core.Group", + ), + ) ] diff --git a/core/models.py b/core/models.py index b94ae1ed..0c1b900e 100644 --- a/core/models.py +++ b/core/models.py @@ -26,7 +26,14 @@ import importlib from django.db import models from django.core.mail import send_mail -from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, UserManager, Group as AuthGroup, GroupManager as AuthGroupManager, AnonymousUser as AuthAnonymousUser +from django.contrib.auth.models import ( + AbstractBaseUser, + PermissionsMixin, + UserManager, + Group as AuthGroup, + GroupManager as AuthGroupManager, + AnonymousUser as AuthAnonymousUser, +) from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.core import validators @@ -59,20 +66,20 @@ class MetaGroupManager(AuthGroupManager): class Group(AuthGroup): is_meta = models.BooleanField( - _('meta group status'), + _("meta group status"), default=False, - help_text=_('Whether a group is a meta group or not'), + help_text=_("Whether a group is a meta group or not"), ) - description = models.CharField(_('description'), max_length=60) + description = models.CharField(_("description"), max_length=60) class Meta: - ordering = ['name'] + ordering = ["name"] def get_absolute_url(self): """ This is needed for black magic powered UpdateView's children """ - return reverse('core:group_list') + return reverse("core:group_list") class MetaGroup(Group): @@ -98,8 +105,8 @@ def validate_promo(value): delta = (date.today() + timedelta(days=180)).year - start_year if value < 0 or delta < value: raise ValidationError( - _('%(value)s is not a valid promo (between 0 and %(end)s)'), - params={'value': value, 'end': delta}, + _("%(value)s is not a valid promo (between 0 and %(end)s)"), + params={"value": value, "end": delta}, ) @@ -114,97 +121,154 @@ class User(AbstractBaseUser): Added field: nick_name, date_of_birth Required fields: email, first_name, last_name, date_of_birth """ + username = models.CharField( - _('username'), + _("username"), max_length=254, unique=True, - help_text=_('Required. 254 characters or fewer. Letters, digits and ./+/-/_ only.'), + help_text=_( + "Required. 254 characters or fewer. Letters, digits and ./+/-/_ only." + ), validators=[ validators.RegexValidator( - r'^[\w.+-]+$', - _('Enter a valid username. This value may contain only ' - 'letters, numbers ' 'and ./+/-/_ characters.') - ), + r"^[\w.+-]+$", + _( + "Enter a valid username. This value may contain only " + "letters, numbers " + "and ./+/-/_ characters." + ), + ) ], - error_messages={ - 'unique': _("A user with that username already exists."), - }, + error_messages={"unique": _("A user with that username already exists.")}, ) - first_name = models.CharField(_('first name'), max_length=64) - last_name = models.CharField(_('last name'), max_length=64) - email = models.EmailField(_('email address'), unique=True) - date_of_birth = models.DateField(_('date of birth'), blank=True, null=True) - nick_name = models.CharField(_('nick name'), max_length=64, null=True, blank=True) + first_name = models.CharField(_("first name"), max_length=64) + last_name = models.CharField(_("last name"), max_length=64) + email = models.EmailField(_("email address"), unique=True) + date_of_birth = models.DateField(_("date of birth"), blank=True, null=True) + nick_name = models.CharField(_("nick name"), max_length=64, null=True, blank=True) is_staff = models.BooleanField( - _('staff status'), + _("staff status"), default=False, - help_text=_('Designates whether the user can log into this admin site.'), + help_text=_("Designates whether the user can log into this admin site."), ) is_active = models.BooleanField( - _('active'), + _("active"), default=True, help_text=_( - 'Designates whether this user should be treated as active. ' - 'Unselect this instead of deleting accounts.' + "Designates whether this user should be treated as active. " + "Unselect this instead of deleting accounts." ), ) - date_joined = models.DateField(_('date joined'), auto_now_add=True) - last_update = models.DateTimeField(_('last update'), auto_now=True) + date_joined = models.DateField(_("date joined"), auto_now_add=True) + last_update = models.DateTimeField(_("last update"), auto_now=True) is_superuser = models.BooleanField( - _('superuser'), + _("superuser"), default=False, - help_text=_( - 'Designates whether this user is a superuser. ' - ), + help_text=_("Designates whether this user is a superuser. "), + ) + groups = models.ManyToManyField(RealGroup, related_name="users", blank=True) + home = models.OneToOneField( + "SithFile", + related_name="home_of", + verbose_name=_("home"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + profile_pict = models.OneToOneField( + "SithFile", + related_name="profile_of", + verbose_name=_("profile"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + avatar_pict = models.OneToOneField( + "SithFile", + related_name="avatar_of", + verbose_name=_("avatar"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + scrub_pict = models.OneToOneField( + "SithFile", + related_name="scrub_of", + verbose_name=_("scrub"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + sex = models.CharField( + _("sex"), + max_length=10, + choices=[("MAN", _("Man")), ("WOMAN", _("Woman"))], + default="MAN", + ) + tshirt_size = models.CharField( + _("tshirt size"), + max_length=5, + choices=[ + ("-", _("-")), + ("XS", _("XS")), + ("S", _("S")), + ("M", _("M")), + ("L", _("L")), + ("XL", _("XL")), + ("XXL", _("XXL")), + ("XXXL", _("XXXL")), + ], + default="-", + ) + role = models.CharField( + _("role"), + max_length=15, + choices=[ + ("STUDENT", _("Student")), + ("ADMINISTRATIVE", _("Administrative agent")), + ("TEACHER", _("Teacher")), + ("AGENT", _("Agent")), + ("DOCTOR", _("Doctor")), + ("FORMER STUDENT", _("Former student")), + ("SERVICE", _("Service")), + ], + blank=True, + default="", + ) + department = models.CharField( + _("department"), + max_length=15, + choices=settings.SITH_PROFILE_DEPARTMENTS, + default="NA", + blank=True, + ) + dpt_option = models.CharField( + _("dpt option"), max_length=32, blank=True, default="" ) - groups = models.ManyToManyField(RealGroup, related_name='users', blank=True) - home = models.OneToOneField('SithFile', related_name='home_of', verbose_name=_("home"), null=True, blank=True, - on_delete=models.SET_NULL) - profile_pict = models.OneToOneField('SithFile', related_name='profile_of', verbose_name=_("profile"), null=True, - blank=True, on_delete=models.SET_NULL) - avatar_pict = models.OneToOneField('SithFile', related_name='avatar_of', verbose_name=_("avatar"), null=True, - blank=True, on_delete=models.SET_NULL) - scrub_pict = models.OneToOneField('SithFile', related_name='scrub_of', verbose_name=_("scrub"), null=True, - blank=True, on_delete=models.SET_NULL) - sex = models.CharField(_("sex"), max_length=10, choices=[("MAN", _("Man")), ("WOMAN", _("Woman"))], default="MAN") - tshirt_size = models.CharField(_("tshirt size"), max_length=5, choices=[ - ("-", _("-")), - ("XS", _("XS")), - ("S", _("S")), - ("M", _("M")), - ("L", _("L")), - ("XL", _("XL")), - ("XXL", _("XXL")), - ("XXXL", _("XXXL")), - ], default="-") - role = models.CharField(_("role"), max_length=15, choices=[ - ("STUDENT", _("Student")), - ("ADMINISTRATIVE", _("Administrative agent")), - ("TEACHER", _("Teacher")), - ("AGENT", _("Agent")), - ("DOCTOR", _("Doctor")), - ("FORMER STUDENT", _("Former student")), - ("SERVICE", _("Service")), - ], blank=True, default="") - department = models.CharField(_("department"), max_length=15, choices=settings.SITH_PROFILE_DEPARTMENTS, - default="NA", blank=True) - dpt_option = models.CharField(_("dpt option"), max_length=32, blank=True, default="") semester = models.CharField(_("semester"), max_length=5, blank=True, default="") quote = models.CharField(_("quote"), max_length=256, blank=True, default="") school = models.CharField(_("school"), max_length=80, blank=True, default="") - promo = models.IntegerField(_("promo"), validators=[validate_promo], null=True, blank=True) - forum_signature = models.TextField(_("forum signature"), max_length=256, blank=True, default="") - second_email = models.EmailField(_('second email address'), null=True, blank=True) + promo = models.IntegerField( + _("promo"), validators=[validate_promo], null=True, blank=True + ) + forum_signature = models.TextField( + _("forum signature"), max_length=256, blank=True, default="" + ) + second_email = models.EmailField(_("second email address"), null=True, blank=True) phone = PhoneNumberField(_("phone"), null=True, blank=True) parent_phone = PhoneNumberField(_("parent phone"), null=True, blank=True) address = models.CharField(_("address"), max_length=128, blank=True, default="") - parent_address = models.CharField(_("parent address"), max_length=128, blank=True, default="") - is_subscriber_viewable = models.BooleanField(_("is subscriber viewable"), default=True) - godfathers = models.ManyToManyField('User', related_name='godchildren', blank=True) + parent_address = models.CharField( + _("parent address"), max_length=128, blank=True, default="" + ) + is_subscriber_viewable = models.BooleanField( + _("is subscriber viewable"), default=True + ) + godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True) objects = UserManager() - USERNAME_FIELD = 'username' + USERNAME_FIELD = "username" # REQUIRED_FIELDS = ['email'] def has_module_perms(self, package_name): @@ -217,7 +281,7 @@ class User(AbstractBaseUser): """ This is needed for black magic powered UpdateView's children """ - return reverse('core:user_profile', kwargs={'user_id': self.pk}) + return reverse("core:user_profile", kwargs={"user_id": self.pk}) def __str__(self): return self.get_display_name() @@ -231,7 +295,9 @@ class User(AbstractBaseUser): @cached_property def is_subscribed(self): - s = self.subscriptions.filter(subscription_start__lte=timezone.now(), subscription_end__gte=timezone.now()) + s = self.subscriptions.filter( + subscription_start__lte=timezone.now(), subscription_end__gte=timezone.now() + ) return s.exists() _club_memberships = {} @@ -265,26 +331,33 @@ class User(AbstractBaseUser): return self.is_subscribed if group_id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID: return self.was_subscribed - if group_name == settings.SITH_MAIN_MEMBERS_GROUP: # We check the subscription if asked + if ( + group_name == settings.SITH_MAIN_MEMBERS_GROUP + ): # We check the subscription if asked return self.is_subscribed - if group_name[-len(settings.SITH_BOARD_SUFFIX):] == settings.SITH_BOARD_SUFFIX: - name = group_name[:-len(settings.SITH_BOARD_SUFFIX)] + if group_name[-len(settings.SITH_BOARD_SUFFIX) :] == settings.SITH_BOARD_SUFFIX: + name = group_name[: -len(settings.SITH_BOARD_SUFFIX)] if name in User._club_memberships.keys(): mem = User._club_memberships[name] else: from club.models import Club + c = Club.objects.filter(unix_name=name).first() mem = c.get_membership_for(self) User._club_memberships[name] = mem if mem: return mem.role > settings.SITH_MAXIMUM_FREE_ROLE return False - if group_name[-len(settings.SITH_MEMBER_SUFFIX):] == settings.SITH_MEMBER_SUFFIX: - name = group_name[:-len(settings.SITH_MEMBER_SUFFIX)] + if ( + group_name[-len(settings.SITH_MEMBER_SUFFIX) :] + == settings.SITH_MEMBER_SUFFIX + ): + name = group_name[: -len(settings.SITH_MEMBER_SUFFIX)] if name in User._club_memberships.keys(): mem = User._club_memberships[name] else: from club.models import Club + c = Club.objects.filter(unix_name=name).first() mem = c.get_membership_for(self) User._club_memberships[name] = mem @@ -297,17 +370,28 @@ class User(AbstractBaseUser): @cached_property def is_root(self): - return self.is_superuser or self.groups.filter(id=settings.SITH_GROUP_ROOT_ID).exists() + return ( + self.is_superuser + or self.groups.filter(id=settings.SITH_GROUP_ROOT_ID).exists() + ) @cached_property def is_board_member(self): from club.models import Club - return Club.objects.filter(unix_name=settings.SITH_MAIN_CLUB['unix_name']).first().has_rights_in_club(self) + + return ( + Club.objects.filter(unix_name=settings.SITH_MAIN_CLUB["unix_name"]) + .first() + .has_rights_in_club(self) + ) @cached_property def can_create_subscription(self): from club.models import Club - for club in Club.objects.filter(id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS).all(): + + for club in Club.objects.filter( + id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS + ).all(): if club.has_rights_in_club(self): return True return False @@ -315,7 +399,14 @@ class User(AbstractBaseUser): @cached_property def is_launderette_manager(self): from club.models import Club - return Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first().get_membership_for(self) + + return ( + Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ) + .first() + .get_membership_for(self) + ) @cached_property def is_banned_alcohol(self): @@ -335,18 +426,34 @@ class User(AbstractBaseUser): else: create = True super(User, self).save(*args, **kwargs) - if create and settings.IS_OLD_MYSQL_PRESENT: # Create user on the old site: TODO remove me! + if ( + create and settings.IS_OLD_MYSQL_PRESENT + ): # Create user on the old site: TODO remove me! import MySQLdb + try: db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) c = db.cursor() - c.execute("""INSERT INTO utilisateurs (id_utilisateur, nom_utl, prenom_utl, email_utl, hash_utl, ae_utl) VALUES - (%s, %s, %s, %s, %s, %s)""", (self.id, self.last_name, self.first_name, self.email, "valid", "0")) + c.execute( + """INSERT INTO utilisateurs (id_utilisateur, nom_utl, prenom_utl, email_utl, hash_utl, ae_utl) VALUES + (%s, %s, %s, %s, %s, %s)""", + ( + self.id, + self.last_name, + self.first_name, + self.email, + "valid", + "0", + ), + ) db.commit() except Exception as e: with open(settings.BASE_DIR + "/user_fail.log", "a") as f: - print("FAIL to add user %s (%s %s - %s) to old site" % (self.id, self.first_name, self.last_name, - self.email), file=f) + print( + "FAIL to add user %s (%s %s - %s) to old site" + % (self.id, self.first_name, self.last_name, self.email), + file=f, + ) print("Reason: %s" % (repr(e)), file=f) db.rollback() @@ -380,7 +487,7 @@ class User(AbstractBaseUser): """ Returns the first_name plus the last_name, with a space in between. """ - full_name = '%s %s' % (self.first_name, self.last_name) + full_name = "%s %s" % (self.first_name, self.last_name) return full_name.strip() def get_short_name(self): @@ -404,7 +511,9 @@ class User(AbstractBaseUser): """ today = timezone.now() born = self.date_of_birth - return today.year - born.year - ((today.month, today.day) < (born.month, born.day)) + return ( + today.year - born.year - ((today.month, today.day) < (born.month, born.day)) + ) def email_user(self, subject, message, from_email=None, **kwargs): """ @@ -420,10 +529,19 @@ class User(AbstractBaseUser): For example: Guy Carlier gives gcarlier, and gcarlier1 if the first one exists Returns the generated username """ + def remove_accents(data): - return ''.join(x for x in unicodedata.normalize('NFKD', data) if - unicodedata.category(x)[0] == 'L').lower() - user_name = remove_accents(self.first_name[0] + self.last_name).encode('ascii', 'ignore').decode('utf-8') + return "".join( + x + for x in unicodedata.normalize("NFKD", data) + if unicodedata.category(x)[0] == "L" + ).lower() + + user_name = ( + remove_accents(self.first_name[0] + self.last_name) + .encode("ascii", "ignore") + .decode("utf-8") + ) un_set = [u.username for u in User.objects.all()] if user_name in un_set: i = 1 @@ -490,7 +608,9 @@ class User(AbstractBaseUser): %s """ % ( - self.profile_pict.get_download_url() if self.profile_pict else staticfiles_storage.url("core/img/unknown.jpg"), + self.profile_pict.get_download_url() + if self.profile_pict + else staticfiles_storage.url("core/img/unknown.jpg"), _("Profile"), escape(self.get_display_name()), ) @@ -514,13 +634,20 @@ class User(AbstractBaseUser): return self._forum_infos except: from forum.models import ForumUserInfo + infos = ForumUserInfo(user=self) infos.save() return infos @cached_property def clubs_with_rights(self): - return [m.club.id for m in self.memberships.filter(models.Q(end_date__isnull=True) | models.Q(end_date__gte=timezone.now())).all() if m.club.has_rights_in_club(self)] + return [ + m.club.id + for m in self.memberships.filter( + models.Q(end_date__isnull=True) | models.Q(end_date__gte=timezone.now()) + ).all() + if m.club.has_rights_in_club(self) + ] @cached_property def is_com_admin(self): @@ -594,9 +721,12 @@ class AnonymousUser(AuthAnonymousUser): return False def can_view(self, obj): - if hasattr(obj, 'view_groups') and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists(): + if ( + hasattr(obj, "view_groups") + and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists() + ): return True - if hasattr(obj, 'can_be_viewed_by') and obj.can_be_viewed_by(self): + if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self): return True return False @@ -607,20 +737,14 @@ class AnonymousUser(AuthAnonymousUser): class Preferences(models.Model): user = models.OneToOneField(User, related_name="_preferences") receive_weekmail = models.BooleanField( - _('do you want to receive the weekmail'), - default=False, - ) - show_my_stats = models.BooleanField( - _('show your stats to others'), - default=False, + _("do you want to receive the weekmail"), default=False ) + show_my_stats = models.BooleanField(_("show your stats to others"), default=False) notify_on_click = models.BooleanField( - _('get a notification for every click'), - default=False, + _("get a notification for every click"), default=False ) notify_on_refill = models.BooleanField( - _('get a notification for every refilling'), - default=False, + _("get a notification for every refilling"), default=False ) def get_display_name(self): @@ -631,40 +755,74 @@ class Preferences(models.Model): def get_directory(instance, filename): - return '.{0}/{1}'.format(instance.get_parent_path(), filename) + return ".{0}/{1}".format(instance.get_parent_path(), filename) def get_compressed_directory(instance, filename): - return './.compressed/{0}/{1}'.format(instance.get_parent_path(), filename) + return "./.compressed/{0}/{1}".format(instance.get_parent_path(), filename) def get_thumbnail_directory(instance, filename): - return './.thumbnails/{0}/{1}'.format(instance.get_parent_path(), filename) + return "./.thumbnails/{0}/{1}".format(instance.get_parent_path(), filename) class SithFile(models.Model): - name = models.CharField(_('file name'), max_length=256, blank=False) - parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True) - file = models.FileField(upload_to=get_directory, verbose_name=_("file"), max_length=256, null=True, blank=True) - compressed = models.FileField(upload_to=get_compressed_directory, verbose_name=_("compressed file"), max_length=256, null=True, blank=True) - thumbnail = models.FileField(upload_to=get_thumbnail_directory, verbose_name=_("thumbnail"), max_length=256, null=True, blank=True) + name = models.CharField(_("file name"), max_length=256, blank=False) + parent = models.ForeignKey( + "self", related_name="children", verbose_name=_("parent"), null=True, blank=True + ) + file = models.FileField( + upload_to=get_directory, + verbose_name=_("file"), + max_length=256, + null=True, + blank=True, + ) + compressed = models.FileField( + upload_to=get_compressed_directory, + verbose_name=_("compressed file"), + max_length=256, + null=True, + blank=True, + ) + thumbnail = models.FileField( + upload_to=get_thumbnail_directory, + verbose_name=_("thumbnail"), + max_length=256, + null=True, + blank=True, + ) owner = models.ForeignKey(User, related_name="owned_files", verbose_name=_("owner")) - edit_groups = models.ManyToManyField(Group, related_name="editable_files", verbose_name=_("edit group"), blank=True) - view_groups = models.ManyToManyField(Group, related_name="viewable_files", verbose_name=_("view group"), blank=True) + edit_groups = models.ManyToManyField( + Group, related_name="editable_files", verbose_name=_("edit group"), blank=True + ) + view_groups = models.ManyToManyField( + Group, related_name="viewable_files", verbose_name=_("view group"), blank=True + ) is_folder = models.BooleanField(_("is folder"), default=True) - mime_type = models.CharField(_('mime type'), max_length=30) + mime_type = models.CharField(_("mime type"), max_length=30) size = models.IntegerField(_("size"), default=0) - date = models.DateTimeField(_('date'), default=timezone.now) + date = models.DateTimeField(_("date"), default=timezone.now) is_moderated = models.BooleanField(_("is moderated"), default=False) - moderator = models.ForeignKey(User, related_name="moderated_files", verbose_name=_("owner"), null=True, blank=True) + moderator = models.ForeignKey( + User, + related_name="moderated_files", + verbose_name=_("owner"), + null=True, + blank=True, + ) asked_for_removal = models.BooleanField(_("asked for removal"), default=False) - is_in_sas = models.BooleanField(_("is in the SAS"), default=False) # Allows to query this flag, updated at each call to save() + is_in_sas = models.BooleanField( + _("is in the SAS"), default=False + ) # Allows to query this flag, updated at each call to save() class Meta: verbose_name = _("file") def is_owned_by(self, user): - if hasattr(self, 'profile_of') and user.is_in_group(settings.SITH_MAIN_BOARD_GROUP): + if hasattr(self, "profile_of") and user.is_in_group( + settings.SITH_MAIN_BOARD_GROUP + ): return True if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID): return True @@ -673,11 +831,11 @@ class SithFile(models.Model): return user.id == self.owner.id def can_be_viewed_by(self, user): - if hasattr(self, 'profile_of'): + if hasattr(self, "profile_of"): return user.can_view(self.profile_of) - if hasattr(self, 'avatar_of'): + if hasattr(self, "avatar_of"): return user.can_view(self.avatar_of) - if hasattr(self, 'scrub_of'): + if hasattr(self, "scrub_of"): return user.can_view(self.scrub_of) return False @@ -696,31 +854,38 @@ class SithFile(models.Model): Cleans up the file """ super(SithFile, self).clean() - if '/' in self.name: + if "/" in self.name: raise ValidationError(_("Character '/' not authorized in name")) if self == self.parent: - raise ValidationError( - _('Loop in folder tree'), - code='loop', - ) - if (self == self.parent or (self.parent is not None and self in self.get_parent_list())): - raise ValidationError( - _('Loop in folder tree'), - code='loop', - ) + raise ValidationError(_("Loop in folder tree"), code="loop") + if self == self.parent or ( + self.parent is not None and self in self.get_parent_list() + ): + raise ValidationError(_("Loop in folder tree"), code="loop") if self.parent and self.parent.is_file: - raise ValidationError(_('You can not make a file be a children of a non folder file')) - if ((self.parent is None and SithFile.objects.exclude(id=self.id).filter(parent=None, name=self.name).exists()) or - (self.parent and self.parent.children.exclude(id=self.id).filter(name=self.name).exists())): raise ValidationError( - _('Duplicate file'), - code='duplicate', + _("You can not make a file be a children of a non folder file") ) + if ( + self.parent is None + and SithFile.objects.exclude(id=self.id) + .filter(parent=None, name=self.name) + .exists() + ) or ( + self.parent + and self.parent.children.exclude(id=self.id).filter(name=self.name).exists() + ): + raise ValidationError(_("Duplicate file"), code="duplicate") if self.is_folder: if self.file: try: import imghdr - if imghdr.what(None, self.file.read()) not in ['gif', 'png', 'jpeg']: + + if imghdr.what(None, self.file.read()) not in [ + "gif", + "png", + "jpeg", + ]: self.file.delete() self.file = None except: @@ -739,9 +904,17 @@ class SithFile(models.Model): if copy_rights: self.copy_rights() if self.is_in_sas: - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_SAS_ADMIN_ID).first().users.all(): - Notification(user=u, url=reverse("sas:moderation"), - type="SAS_MODERATION", param="1").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_SAS_ADMIN_ID) + .first() + .users.all() + ): + Notification( + user=u, + url=reverse("sas:moderation"), + type="SAS_MODERATION", + param="1", + ).save() def apply_rights_recursively(self, only_folders=False): children = self.children.all() @@ -789,7 +962,7 @@ class SithFile(models.Model): parent_full_path = settings.MEDIA_ROOT + parent_path print("Parent full path: %s" % parent_full_path) os.makedirs(parent_full_path, exist_ok=True) - old_path = self.file.name # Should be relative: "./users/skia/bleh.jpg" + old_path = self.file.name # Should be relative: "./users/skia/bleh.jpg" new_path = "." + self.get_full_path() print("Old path: %s " % old_path) print("New path: %s " % new_path) @@ -802,14 +975,17 @@ class SithFile(models.Model): print("New file path: %s " % self.file.path) # Really move at the FS level if os.path.exists(parent_full_path): - os.rename(settings.MEDIA_ROOT + old_path, settings.MEDIA_ROOT + new_path) + os.rename( + settings.MEDIA_ROOT + old_path, + settings.MEDIA_ROOT + new_path, + ) # Empty directories may remain, but that's not really a # problem, and that can be solved with a simple shell # command: `find . -type d -empty -delete` except Exception as e: print("This file likely had a problem. Here is the exception:") print(repr(e)) - print('-'*80) + print("-" * 80) def _check_path_consistence(self): file_path = str(self.file) @@ -817,12 +993,12 @@ class SithFile(models.Model): db_path = ".%s" % self.get_full_path() if not os.path.exists(file_full_path): print("%s: WARNING: real file does not exists!" % self.id) - print("file path: %s" % file_path, end='') + print("file path: %s" % file_path, end="") print(" db path: %s" % db_path) return False if file_path != db_path: - print("%s: " % self.id, end='') - print("file path: %s" % file_path, end='') + print("%s: " % self.id, end="") + print("file path: %s" % file_path, end="") print(" db path: %s" % db_path) return False print("%s OK (%s)" % (self.id, file_path)) @@ -845,11 +1021,13 @@ class SithFile(models.Model): @cached_property def as_picture(self): from sas.models import Picture + return Picture.objects.filter(id=self.id).first() @cached_property def as_album(self): from sas.models import Album + return Album.objects.filter(id=self.id).first() def __str__(self): @@ -867,16 +1045,16 @@ class SithFile(models.Model): return l def get_parent_path(self): - return '/' + '/'.join([p.name for p in self.get_parent_list()[::-1]]) + return "/" + "/".join([p.name for p in self.get_parent_list()[::-1]]) def get_full_path(self): - return self.get_parent_path() + '/' + self.name + return self.get_parent_path() + "/" + self.name def get_display_name(self): return self.name def get_download_url(self): - return reverse('core:download', kwargs={'file_id': self.id}) + return reverse("core:download", kwargs={"file_id": self.id}) def __str__(self): return self.get_parent_path() + "/" + self.name @@ -884,16 +1062,19 @@ class SithFile(models.Model): class LockError(Exception): """There was a lock error on the object""" + pass class AlreadyLocked(LockError): """The object is already locked""" + pass class NotLocked(LockError): """The object is not locked""" + pass @@ -908,29 +1089,63 @@ class Page(models.Model): Be careful with the _full_name attribute: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when playing with a Page object, use get_full_name() instead! """ - name = models.CharField(_('page unix name'), max_length=30, - validators=[ - validators.RegexValidator( - r'^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$', - _('Enter a valid page name. This value may contain only ' - 'unaccented letters, numbers ' 'and ./+/-/_ characters.') - ), ], - blank=False) - parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True, on_delete=models.SET_NULL) + + name = models.CharField( + _("page unix name"), + max_length=30, + validators=[ + validators.RegexValidator( + r"^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$", + _( + "Enter a valid page name. This value may contain only " + "unaccented letters, numbers " + "and ./+/-/_ characters." + ), + ) + ], + blank=False, + ) + parent = models.ForeignKey( + "self", + related_name="children", + verbose_name=_("parent"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) # Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when # playing with a Page object, use get_full_name() instead! - _full_name = models.CharField(_('page name'), max_length=255, blank=True) + _full_name = models.CharField(_("page name"), max_length=255, blank=True) # This function prevents generating migration upon settings change - def get_default_owner_group(): return settings.SITH_GROUP_ROOT_ID - owner_group = models.ForeignKey(Group, related_name="owned_page", verbose_name=_("owner group"), - default=get_default_owner_group) - edit_groups = models.ManyToManyField(Group, related_name="editable_page", verbose_name=_("edit group"), blank=True) - view_groups = models.ManyToManyField(Group, related_name="viewable_page", verbose_name=_("view group"), blank=True) - lock_user = models.ForeignKey(User, related_name="locked_pages", verbose_name=_("lock user"), blank=True, null=True, default=None) - lock_timeout = models.DateTimeField(_('lock_timeout'), null=True, blank=True, default=None) + def get_default_owner_group(): + return settings.SITH_GROUP_ROOT_ID + + owner_group = models.ForeignKey( + Group, + related_name="owned_page", + verbose_name=_("owner group"), + default=get_default_owner_group, + ) + edit_groups = models.ManyToManyField( + Group, related_name="editable_page", verbose_name=_("edit group"), blank=True + ) + view_groups = models.ManyToManyField( + Group, related_name="viewable_page", verbose_name=_("view group"), blank=True + ) + lock_user = models.ForeignKey( + User, + related_name="locked_pages", + verbose_name=_("lock user"), + blank=True, + null=True, + default=None, + ) + lock_timeout = models.DateTimeField( + _("lock_timeout"), null=True, blank=True, default=None + ) class Meta: - unique_together = ('name', 'parent') + unique_together = ("name", "parent") permissions = ( ("change_prop_page", "Can change the page's properties (groups, ...)"), ("view_page", "Can view the page"), @@ -950,22 +1165,20 @@ class Page(models.Model): """ Cleans up only the name for the moment, but this can be used to make any treatment before saving the object """ - if '/' in self.name: - self.name = self.name.split('/')[-1] - if Page.objects.exclude(pk=self.pk).filter(_full_name=self.get_full_name()).exists(): - raise ValidationError( - _('Duplicate page'), - code='duplicate', - ) + if "/" in self.name: + self.name = self.name.split("/")[-1] + if ( + Page.objects.exclude(pk=self.pk) + .filter(_full_name=self.get_full_name()) + .exists() + ): + raise ValidationError(_("Duplicate page"), code="duplicate") super(Page, self).clean() if self.parent is not None and self in self.get_parent_list(): - raise ValidationError( - _('Loop in page tree'), - code='loop', - ) + raise ValidationError(_("Loop in page tree"), code="loop") def can_be_edited_by(self, user): - if hasattr(self, 'club') and self.club.can_be_edited_by(user): + if hasattr(self, "club") and self.club.can_be_edited_by(user): # Override normal behavior for clubs return True if self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member: @@ -989,14 +1202,16 @@ class Page(models.Model): """ Performs some needed actions before and after saving a page in database """ - locked = kwargs.pop('force_lock', False) + locked = kwargs.pop("force_lock", False) if not locked: locked = self.is_locked() if not locked: raise NotLocked("The page is not locked and thus can not be saved") self.full_clean() if not self.id: - super(Page, self).save(*args, **kwargs) # Save a first time to correctly set _full_name + super(Page, self).save( + *args, **kwargs + ) # Save a first time to correctly set _full_name # This reset the _full_name just before saving to maintain a coherent field quicker for queries than the # recursive method # It also update all the children to maintain correct names @@ -1012,10 +1227,16 @@ class Page(models.Model): This is where the timeout is handled, so a locked page for which the timeout is reach will be unlocked and this function will return False """ - if self.lock_timeout and (timezone.now() - self.lock_timeout > timedelta(minutes=5)): + if self.lock_timeout and ( + timezone.now() - self.lock_timeout > timedelta(minutes=5) + ): # print("Lock timed out") self.unset_lock() - return self.lock_user and self.lock_timeout and (timezone.now() - self.lock_timeout < timedelta(minutes=5)) + return ( + self.lock_user + and self.lock_timeout + and (timezone.now() - self.lock_timeout < timedelta(minutes=5)) + ) def set_lock(self, user): """ @@ -1063,7 +1284,7 @@ class Page(models.Model): """ This is needed for black magic powered UpdateView's children """ - return reverse('core:page', kwargs={'page_name': self._full_name}) + return reverse("core:page", kwargs={"page_name": self._full_name}) def __str__(self): return self.get_full_name() @@ -1076,7 +1297,7 @@ class Page(models.Model): """ if self.parent is None: return self.name - return '/'.join([self.parent.get_full_name(), self.name]) + return "/".join([self.parent.get_full_name(), self.name]) def get_display_name(self): try: @@ -1087,7 +1308,9 @@ class Page(models.Model): @cached_property def is_club_page(self): club_root_page = Page.objects.filter(name=settings.SITH_CLUB_ROOT_PAGE).first() - return club_root_page is not None and (self == club_root_page or club_root_page in self.get_parent_list()) + return club_root_page is not None and ( + self == club_root_page or club_root_page in self.get_parent_list() + ) @cached_property def need_club_redirection(self): @@ -1111,21 +1334,22 @@ class PageRev(models.Model): is the real content of the page. The content is in PageRev.title and PageRev.content . """ + revision = models.IntegerField(_("revision")) title = models.CharField(_("page title"), max_length=255, blank=True) content = models.TextField(_("page content"), blank=True) - date = models.DateTimeField(_('date'), auto_now=True) - author = models.ForeignKey(User, related_name='page_rev') - page = models.ForeignKey(Page, related_name='revisions') + date = models.DateTimeField(_("date"), auto_now=True) + author = models.ForeignKey(User, related_name="page_rev") + page = models.ForeignKey(Page, related_name="revisions") class Meta: - ordering = ['date', ] + ordering = ["date"] def get_absolute_url(self): """ This is needed for black magic powered UpdateView's children """ - return reverse('core:page', kwargs={'page_name': self.page._full_name}) + return reverse("core:page", kwargs={"page_name": self.page._full_name}) def __str__(self): return str(self.__dict__) @@ -1154,12 +1378,14 @@ class PageRev(models.Model): class Notification(models.Model): - user = models.ForeignKey(User, related_name='notifications') + user = models.ForeignKey(User, related_name="notifications") url = models.CharField(_("url"), max_length=255) param = models.CharField(_("param"), max_length=128, default="") - type = models.CharField(_("type"), max_length=32, choices=settings.SITH_NOTIFICATIONS, default="GENERIC") - date = models.DateTimeField(_('date'), default=timezone.now) - viewed = models.BooleanField(_('viewed'), default=False) + type = models.CharField( + _("type"), max_length=32, choices=settings.SITH_NOTIFICATIONS, default="GENERIC" + ) + date = models.DateTimeField(_("date"), default=timezone.now) + viewed = models.BooleanField(_("viewed"), default=False) def __str__(self): if self.param: @@ -1169,7 +1395,9 @@ class Notification(models.Model): def callback(self): # Get the callback defined in settings to update existing # notifications - mod_name, func_name = settings.SITH_PERMANENT_NOTIFICATIONS[self.type].rsplit('.',1) + mod_name, func_name = settings.SITH_PERMANENT_NOTIFICATIONS[self.type].rsplit( + ".", 1 + ) mod = importlib.import_module(mod_name) getattr(mod, func_name)(self) @@ -1184,16 +1412,18 @@ class Notification(models.Model): class Gift(models.Model): - label = models.CharField(_('label'), max_length=255) - date = models.DateTimeField(_('date'), default=timezone.now) - user = models.ForeignKey(User, related_name='gifts') + label = models.CharField(_("label"), max_length=255) + date = models.DateTimeField(_("date"), default=timezone.now) + user = models.ForeignKey(User, related_name="gifts") def __str__(self): - return "%s - %s" % (self.translated_label, self.date.strftime('%d %b %Y')) + return "%s - %s" % (self.translated_label, self.date.strftime("%d %b %Y")) @property def translated_label(self): - translations = [label[1] for label in settings.SITH_GIFT_LIST if label[0] == self.label] + translations = [ + label[1] for label in settings.SITH_GIFT_LIST if label[0] == self.label + ] if len(translations) > 0: return translations[0] return self.label diff --git a/core/operations.py b/core/operations.py index ea847fd9..3c6eae9b 100644 --- a/core/operations.py +++ b/core/operations.py @@ -46,5 +46,5 @@ class PsqlRunOnly(migrations.RunSQL): """ def _run_sql(self, schema_editor, sqls): - if connection.vendor == 'postgresql': + if connection.vendor == "postgresql": super(PsqlRunOnly, self)._run_sql(schema_editor, sqls) diff --git a/core/scss/finder.py b/core/scss/finder.py index c9ef0859..3e25279f 100644 --- a/core/scss/finder.py +++ b/core/scss/finder.py @@ -34,21 +34,20 @@ class ScssFinder(FileSystemFinder): """ Find static *.css files compiled on the fly """ + locations = [] def __init__(self, apps=None, *args, **kwargs): location = settings.STATIC_ROOT if not os.path.isdir(location): return - self.locations = [ - ('', location), - ] + self.locations = [("", location)] self.storages = OrderedDict() filesystem_storage = FileSystemStorage(location=location) filesystem_storage.prefix = self.locations[0][0] self.storages[location] = filesystem_storage def find(self, path, all=False): - if path.endswith('.css'): + if path.endswith(".css"): return super(ScssFinder, self).find(path, all) return [] diff --git a/core/scss/processor.py b/core/scss/processor.py index dab0de4c..39f099f9 100644 --- a/core/scss/processor.py +++ b/core/scss/processor.py @@ -39,7 +39,8 @@ class ScssProcessor(object): Else : give the path of the corresponding css supposed to already be compiled Don't forget to use compilestatics to compile scss for production """ - prefix = iri_to_uri(getattr(settings, 'STATIC_URL', '/static/')) + + prefix = iri_to_uri(getattr(settings, "STATIC_URL", "/static/")) storage = ScssFileStorage() scss_extensions = [".scss"] @@ -63,7 +64,7 @@ class ScssProcessor(object): "include_paths": settings.SASS_INCLUDE_FOLDERS, } if settings.SASS_PRECISION: - compile_args['precision'] = settings.SASS_PRECISION + compile_args["precision"] = settings.SASS_PRECISION content = sass.compile(**compile_args) content = force_bytes(content) diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py index 0a9419f8..0ace29c4 100644 --- a/core/templatetags/__init__.py +++ b/core/templatetags/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/core/templatetags/renderer.py b/core/templatetags/renderer.py index 5d2a061c..fe982c60 100644 --- a/core/templatetags/renderer.py +++ b/core/templatetags/renderer.py @@ -38,11 +38,11 @@ register = template.Library() @register.filter(is_safe=False) @stringfilter def markdown(text): - return mark_safe("
    %s
    " % md(text)) + return mark_safe('
    %s
    ' % md(text)) -@register.filter(name='phonenumber') -def phonenumber(value, country='FR', - format=phonenumbers.PhoneNumberFormat.NATIONAL): + +@register.filter(name="phonenumber") +def phonenumber(value, country="FR", format=phonenumbers.PhoneNumberFormat.NATIONAL): """ This filter is kindly borrowed from https://github.com/foundertherapy/django-phonenumber-filter """ @@ -53,13 +53,37 @@ def phonenumber(value, country='FR', except phonenumbers.NumberParseException as e: return value + @register.filter() @stringfilter def datetime_format_python_to_PHP(python_format_string): """ Given a python datetime format string, attempts to convert it to the nearest PHP datetime format string possible. """ - python2PHP = {"%a": "D", "%a": "D", "%A": "l", "%b": "M", "%B": "F", "%c": "", "%d": "d", "%H": "H", "%I": "h", "%j": "z", "%m": "m", "%M": "i", "%p": "A", "%S": "s", "%U": "", "%w": "w", "%W": "W", "%x": "", "%X": "", "%y": "y", "%Y": "Y", "%Z": "e"} + python2PHP = { + "%a": "D", + "%a": "D", + "%A": "l", + "%b": "M", + "%B": "F", + "%c": "", + "%d": "d", + "%H": "H", + "%I": "h", + "%j": "z", + "%m": "m", + "%M": "i", + "%p": "A", + "%S": "s", + "%U": "", + "%w": "w", + "%W": "W", + "%x": "", + "%X": "", + "%y": "y", + "%Y": "Y", + "%Z": "e", + } php_format_string = python_format_string for py, php in python2PHP.items(): diff --git a/core/tests.py b/core/tests.py index 5b93fd71..92e41a98 100644 --- a/core/tests.py +++ b/core/tests.py @@ -49,203 +49,261 @@ class UserRegistrationTest(TestCase): Should register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'guy@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "guy@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_OK' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_OK" in str(response.content)) def test_register_user_form_fail_password(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop2', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop2", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_email(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'bibou.git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "bibou.git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_missing_name(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': '', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_missing_date_of_birth(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': '', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_missing_first_name(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': '', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_wrong_captcha(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': 'Bibou', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'WRONG_CAPTCHA' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "Bibou", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "WRONG_CAPTCHA", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_already_exists(self): """ Should not register a user correctly """ c = Client() - c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) - response = c.post(reverse('core:register'), {'first_name': 'Bibou', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) + response = c.post( + reverse("core:register"), + { + "first_name": "Bibou", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_login_success(self): """ Should login a user correctly """ c = Client() - c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) - response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'plop'}) + c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) + response = c.post( + reverse("core:login"), {"username": "gcarlier", "password": "plop"} + ) self.assertTrue(response.status_code == 302) - #self.assertTrue('Hello, world' in str(response.content)) + # self.assertTrue('Hello, world' in str(response.content)) def test_login_fail(self): """ Should not login a user correctly """ c = Client() - c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) - response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'guy'}) + c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) + response = c.post( + reverse("core:login"), {"username": "gcarlier", "password": "guy"} + ) self.assertTrue(response.status_code == 200) - self.assertTrue("""

    Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.

    """ in str(response.content)) + self.assertTrue( + """

    Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.

    """ + in str(response.content) + ) + class MarkdownTest(TestCase): def test_full_markdown_syntax(self): root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as md_file: + with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md_file: md = md_file.read() - with open(os.path.join(root_path) + '/doc/SYNTAX.html', 'r') as html_file: + with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file: html = html_file.read() result = markdown(md) self.assertTrue(result == html) + class PageHandlingTest(TestCase): def setUp(self): try: Group.objects.create(name="root") - u = User(username='root', last_name="", first_name="Bibou", - email="ae.info@utbm.fr", - date_of_birth="1942-06-12", - is_superuser=True, is_staff=True) + u = User( + username="root", + last_name="", + first_name="Bibou", + email="ae.info@utbm.fr", + date_of_birth="1942-06-12", + is_superuser=True, + is_staff=True, + ) u.set_password("plop") u.save() - self.client.login(username='root', password='plop') + self.client.login(username="root", password="plop") except Exception as e: print(e) @@ -253,12 +311,10 @@ class PageHandlingTest(TestCase): """ Should create a page correctly """ - self.client.post(reverse('core:page_new'), { - 'parent': '', - 'name': 'guy', - 'owner_group': 1, - }) - response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'})) + self.client.post( + reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": 1} + ) + response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"})) self.assertTrue(response.status_code == 200) self.assertTrue('' in str(response.content)) @@ -266,17 +322,16 @@ class PageHandlingTest(TestCase): """ Should create a page correctly """ - self.client.post(reverse('core:page_new'), { - 'parent': '', - 'name': 'guy', - 'owner_group': '1', - }) - response = self.client.post(reverse('core:page_new'), { - 'parent': '1', - 'name': 'bibou', - 'owner_group': '1', - }) - response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy/bibou'})) + self.client.post( + reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": "1"} + ) + response = self.client.post( + reverse("core:page_new"), + {"parent": "1", "name": "bibou", "owner_group": "1"}, + ) + response = self.client.get( + reverse("core:page", kwargs={"page_name": "guy/bibou"}) + ) self.assertTrue(response.status_code == 200) self.assertTrue('' in str(response.content)) @@ -286,17 +341,24 @@ class PageHandlingTest(TestCase): """ parent = Page(name="guy", owner_group=Group.objects.filter(id=1).first()) parent.save(force_lock=True) - page = Page(name="bibou", owner_group=Group.objects.filter(id=1).first(), parent=parent) + page = Page( + name="bibou", owner_group=Group.objects.filter(id=1).first(), parent=parent + ) page.save(force_lock=True) - response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy/bibou'})) + response = self.client.get( + reverse("core:page", kwargs={"page_name": "guy/bibou"}) + ) self.assertTrue(response.status_code == 200) - self.assertTrue('\\xc3\\x89diter' in str(response.content)) + self.assertTrue( + '\\xc3\\x89diter' + in str(response.content) + ) def test_access_page_not_found(self): """ Should not display a page correctly """ - response = self.client.get(reverse('core:page', kwargs={'page_name': 'swagg'})) + response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"})) response = self.client.get("/page/swagg/") self.assertTrue(response.status_code == 200) self.assertTrue('' in str(response.content)) @@ -305,15 +367,14 @@ class PageHandlingTest(TestCase): """ Should format the markdown and escape html correctly """ - self.client.post(reverse('core:page_new'), { - 'parent': '', - 'name': 'guy', - 'owner_group': '1', - }) - self.client.post(reverse('core:page_edit', kwargs={'page_name': 'guy'}), { - 'title': 'Bibou', - 'content': - '''Guy *bibou* + self.client.post( + reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": "1"} + ) + self.client.post( + reverse("core:page_edit", kwargs={"page_name": "guy"}), + { + "title": "Bibou", + "content": """Guy *bibou* http://git.an @@ -322,13 +383,18 @@ http://git.an Bibou -''', - }) - response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'})) +""", + }, + ) + response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"})) self.assertTrue(response.status_code == 200) - self.assertTrue('

    Guy bibou

    \\n

    http://git.an

    \\n' + - '

    Swag

    \\n<guy>Bibou</guy>' + - "<script>alert(\\'Guy\\');</script>" in str(response.content)) + self.assertTrue( + '

    Guy bibou

    \\n

    http://git.an

    \\n' + + "

    Swag

    \\n<guy>Bibou</guy>" + + "<script>alert(\\'Guy\\');</script>" + in str(response.content) + ) + # TODO: many tests on the pages: # - renaming a page @@ -341,23 +407,33 @@ class FileHandlingTest(TestCase): try: call_command("populate") self.subscriber = User.objects.filter(username="subscriber").first() - self.client.login(username='subscriber', password='plop') + self.client.login(username="subscriber", password="plop") except Exception as e: print(e) def test_create_folder_home(self): - response = self.client.post(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}), - {"folder_name": "GUY_folder_test"}) + response = self.client.post( + reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}), + {"folder_name": "GUY_folder_test"}, + ) self.assertTrue(response.status_code == 302) - response = self.client.get(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})) + response = self.client.get( + reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}) + ) self.assertTrue(response.status_code == 200) self.assertTrue("GUY_folder_test" in str(response.content)) def test_upload_file_home(self): with open("/bin/ls", "rb") as f: - response = self.client.post(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}), - {"file_field": f}) + response = self.client.post( + reverse( + "core:file_detail", kwargs={"file_id": self.subscriber.home.id} + ), + {"file_field": f}, + ) self.assertTrue(response.status_code == 302) - response = self.client.get(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})) + response = self.client.get( + reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}) + ) self.assertTrue(response.status_code == 200) self.assertTrue("ls" in str(response.content)) diff --git a/core/urls.py b/core/urls.py index afbcbf40..be18ff8e 100644 --- a/core/urls.py +++ b/core/urls.py @@ -28,73 +28,181 @@ from django.conf.urls import url from core.views import * urlpatterns = [ - url(r'^$', index, name='index'), - url(r'^to_markdown$', ToMarkdownView.as_view(), name='to_markdown'), - url(r'^notifications$', NotificationList.as_view(), name='notification_list'), - url(r'^notification/(?P[0-9]+)$', notification, name='notification'), - + url(r"^$", index, name="index"), + url(r"^to_markdown$", ToMarkdownView.as_view(), name="to_markdown"), + url(r"^notifications$", NotificationList.as_view(), name="notification_list"), + url(r"^notification/(?P[0-9]+)$", notification, name="notification"), # Search - url(r'^search/$', search_view, name='search'), - url(r'^search_json/$', search_json, name='search_json'), - url(r'^search_user/$', search_user_json, name='search_user'), - + url(r"^search/$", search_view, name="search"), + url(r"^search_json/$", search_json, name="search_json"), + url(r"^search_user/$", search_user_json, name="search_user"), # Login and co - url(r'^login/$', login, name='login'), - url(r'^logout/$', logout, name='logout'), - url(r'^password_change/$', password_change, name='password_change'), - url(r'^password_change/(?P[0-9]+)$', password_root_change, name='password_root_change'), - url(r'^password_change/done$', password_change_done, name='password_change_done'), - url(r'^password_reset/$', password_reset, name='password_reset'), - url(r'^password_reset/done$', password_reset_done, name='password_reset_done'), - url(r'^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', password_reset_confirm, name='password_reset_confirm'), - url(r'^reset/done/$', password_reset_complete, name='password_reset_complete'), - url(r'^register$', register, name='register'), - + url(r"^login/$", login, name="login"), + url(r"^logout/$", logout, name="logout"), + url(r"^password_change/$", password_change, name="password_change"), + url( + r"^password_change/(?P[0-9]+)$", + password_root_change, + name="password_root_change", + ), + url(r"^password_change/done$", password_change_done, name="password_change_done"), + url(r"^password_reset/$", password_reset, name="password_reset"), + url(r"^password_reset/done$", password_reset_done, name="password_reset_done"), + url( + r"^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$", + password_reset_confirm, + name="password_reset_confirm", + ), + url(r"^reset/done/$", password_reset_complete, name="password_reset_complete"), + url(r"^register$", register, name="register"), # Group handling - url(r'^group/$', GroupListView.as_view(), name='group_list'), - url(r'^group/new$', GroupCreateView.as_view(), name='group_new'), - url(r'^group/(?P[0-9]+)/$', GroupEditView.as_view(), name='group_edit'), - url(r'^group/(?P[0-9]+)/delete$', GroupDeleteView.as_view(), name='group_delete'), - + url(r"^group/$", GroupListView.as_view(), name="group_list"), + url(r"^group/new$", GroupCreateView.as_view(), name="group_new"), + url(r"^group/(?P[0-9]+)/$", GroupEditView.as_view(), name="group_edit"), + url( + r"^group/(?P[0-9]+)/delete$", + GroupDeleteView.as_view(), + name="group_delete", + ), # User views - url(r'^user/$', UserListView.as_view(), name='user_list'), - url(r'^user/(?P[0-9]+)/mini$', UserMiniView.as_view(), name='user_profile_mini'), - url(r'^user/(?P[0-9]+)/$', UserView.as_view(), name='user_profile'), - url(r'^user/(?P[0-9]+)/pictures$', UserPicturesView.as_view(), name='user_pictures'), - url(r'^user/(?P[0-9]+)/godfathers$', UserGodfathersView.as_view(), name='user_godfathers'), - url(r'^user/(?P[0-9]+)/godfathers/tree$', UserGodfathersTreeView.as_view(), name='user_godfathers_tree'), - url(r'^user/(?P[0-9]+)/godfathers/tree/pict$', UserGodfathersTreePictureView.as_view(), name='user_godfathers_tree_pict'), - url(r'^user/(?P[0-9]+)/godfathers/(?P[0-9]+)/(?P(True)|(False))/delete$', DeleteUserGodfathers, name='user_godfathers_delete'), - url(r'^user/(?P[0-9]+)/edit$', UserUpdateProfileView.as_view(), name='user_edit'), - url(r'^user/(?P[0-9]+)/profile_upload$', UserUploadProfilePictView.as_view(), name='user_profile_upload'), - url(r'^user/(?P[0-9]+)/clubs$', UserClubView.as_view(), name='user_clubs'), - url(r'^user/(?P[0-9]+)/prefs$', UserPreferencesView.as_view(), name='user_prefs'), - url(r'^user/(?P[0-9]+)/groups$', UserUpdateGroupView.as_view(), name='user_groups'), - url(r'^user/tools/$', UserToolsView.as_view(), name='user_tools'), - url(r'^user/(?P[0-9]+)/account$', UserAccountView.as_view(), name='user_account'), - url(r'^user/(?P[0-9]+)/account/(?P[0-9]+)/(?P[0-9]+)$', UserAccountDetailView.as_view(), name='user_account_detail'), - url(r'^user/(?P[0-9]+)/stats$', UserStatsView.as_view(), name='user_stats'), - url(r'^user/(?P[0-9]+)/gift/create$', GiftCreateView.as_view(), name='user_gift_create'), - url(r'^user/(?P[0-9]+)/gift/delete/(?P[0-9]+)/$', GiftDeleteView.as_view(), name='user_gift_delete'), - + url(r"^user/$", UserListView.as_view(), name="user_list"), + url( + r"^user/(?P[0-9]+)/mini$", + UserMiniView.as_view(), + name="user_profile_mini", + ), + url(r"^user/(?P[0-9]+)/$", UserView.as_view(), name="user_profile"), + url( + r"^user/(?P[0-9]+)/pictures$", + UserPicturesView.as_view(), + name="user_pictures", + ), + url( + r"^user/(?P[0-9]+)/godfathers$", + UserGodfathersView.as_view(), + name="user_godfathers", + ), + url( + r"^user/(?P[0-9]+)/godfathers/tree$", + UserGodfathersTreeView.as_view(), + name="user_godfathers_tree", + ), + url( + r"^user/(?P[0-9]+)/godfathers/tree/pict$", + UserGodfathersTreePictureView.as_view(), + name="user_godfathers_tree_pict", + ), + url( + r"^user/(?P[0-9]+)/godfathers/(?P[0-9]+)/(?P(True)|(False))/delete$", + DeleteUserGodfathers, + name="user_godfathers_delete", + ), + url( + r"^user/(?P[0-9]+)/edit$", + UserUpdateProfileView.as_view(), + name="user_edit", + ), + url( + r"^user/(?P[0-9]+)/profile_upload$", + UserUploadProfilePictView.as_view(), + name="user_profile_upload", + ), + url(r"^user/(?P[0-9]+)/clubs$", UserClubView.as_view(), name="user_clubs"), + url( + r"^user/(?P[0-9]+)/prefs$", + UserPreferencesView.as_view(), + name="user_prefs", + ), + url( + r"^user/(?P[0-9]+)/groups$", + UserUpdateGroupView.as_view(), + name="user_groups", + ), + url(r"^user/tools/$", UserToolsView.as_view(), name="user_tools"), + url( + r"^user/(?P[0-9]+)/account$", + UserAccountView.as_view(), + name="user_account", + ), + url( + r"^user/(?P[0-9]+)/account/(?P[0-9]+)/(?P[0-9]+)$", + UserAccountDetailView.as_view(), + name="user_account_detail", + ), + url( + r"^user/(?P[0-9]+)/stats$", UserStatsView.as_view(), name="user_stats" + ), + url( + r"^user/(?P[0-9]+)/gift/create$", + GiftCreateView.as_view(), + name="user_gift_create", + ), + url( + r"^user/(?P[0-9]+)/gift/delete/(?P[0-9]+)/$", + GiftDeleteView.as_view(), + name="user_gift_delete", + ), # File views # url(r'^file/add/(?Ppopup)?$', FileCreateView.as_view(), name='file_new'), - url(r'^file/(?Ppopup)?$', FileListView.as_view(), name='file_list'), - url(r'^file/(?P[0-9]+)/(?Ppopup)?$', FileView.as_view(), name='file_detail'), - url(r'^file/(?P[0-9]+)/edit/(?Ppopup)?$', FileEditView.as_view(), name='file_edit'), - url(r'^file/(?P[0-9]+)/prop/(?Ppopup)?$', FileEditPropView.as_view(), name='file_prop'), - url(r'^file/(?P[0-9]+)/delete/(?Ppopup)?$', FileDeleteView.as_view(), name='file_delete'), - url(r'^file/moderation$', FileModerationView.as_view(), name='file_moderation'), - url(r'^file/(?P[0-9]+)/moderate$', FileModerateView.as_view(), name='file_moderate'), - url(r'^file/(?P[0-9]+)/download$', send_file, name='download'), - + url(r"^file/(?Ppopup)?$", FileListView.as_view(), name="file_list"), + url( + r"^file/(?P[0-9]+)/(?Ppopup)?$", + FileView.as_view(), + name="file_detail", + ), + url( + r"^file/(?P[0-9]+)/edit/(?Ppopup)?$", + FileEditView.as_view(), + name="file_edit", + ), + url( + r"^file/(?P[0-9]+)/prop/(?Ppopup)?$", + FileEditPropView.as_view(), + name="file_prop", + ), + url( + r"^file/(?P[0-9]+)/delete/(?Ppopup)?$", + FileDeleteView.as_view(), + name="file_delete", + ), + url(r"^file/moderation$", FileModerationView.as_view(), name="file_moderation"), + url( + r"^file/(?P[0-9]+)/moderate$", + FileModerateView.as_view(), + name="file_moderate", + ), + url(r"^file/(?P[0-9]+)/download$", send_file, name="download"), # Page views - url(r'^page/$', PageListView.as_view(), name='page_list'), - url(r'^page/create$', PageCreateView.as_view(), name='page_new'), - url(r'^page/(?P[0-9]*)/delete$', PageDeleteView.as_view(), name='page_delete'), - url(r'^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/edit$', PageEditView.as_view(), name='page_edit'), - url(r'^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/prop$', PagePropView.as_view(), name='page_prop'), - url(r'^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/hist$', PageHistView.as_view(), name='page_hist'), - url(r'^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/rev/(?P[0-9]+)/', PageRevView.as_view(), name='page_rev'), - url(r'^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/$', PageView.as_view(), name='page'), + url(r"^page/$", PageListView.as_view(), name="page_list"), + url(r"^page/create$", PageCreateView.as_view(), name="page_new"), + url( + r"^page/(?P[0-9]*)/delete$", + PageDeleteView.as_view(), + name="page_delete", + ), + url( + r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/edit$", + PageEditView.as_view(), + name="page_edit", + ), + url( + r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/prop$", + PagePropView.as_view(), + name="page_prop", + ), + url( + r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/hist$", + PageHistView.as_view(), + name="page_hist", + ), + url( + r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/rev/(?P[0-9]+)/", + PageRevView.as_view(), + name="page_rev", + ), + url( + r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/$", + PageView.as_view(), + name="page", + ), ] diff --git a/core/utils.py b/core/utils.py index f375a522..6267802c 100644 --- a/core/utils.py +++ b/core/utils.py @@ -30,6 +30,7 @@ from io import BytesIO from datetime import date from PIL import ExifTags + # from exceptions import IOError import PIL @@ -71,9 +72,9 @@ def get_semester(d=date.today()): def scale_dimension(width, height, long_edge): if width > height: - ratio = long_edge * 1. / width + ratio = long_edge * 1.0 / width else: - ratio = long_edge * 1. / height + ratio = long_edge * 1.0 / height return int(width * ratio), int(height * ratio) @@ -83,16 +84,28 @@ def resize_image(im, edge, format): content = BytesIO() im = im.resize((width, height), PIL.Image.ANTIALIAS) try: - im.save(fp=content, format=format.upper(), quality=90, optimize=True, progressive=True) + im.save( + fp=content, + format=format.upper(), + quality=90, + optimize=True, + progressive=True, + ) except IOError: PIL.ImageFile.MAXBLOCK = im.size[0] * im.size[1] - im.save(fp=content, format=format.upper(), quality=90, optimize=True, progressive=True) + im.save( + fp=content, + format=format.upper(), + quality=90, + optimize=True, + progressive=True, + ) return ContentFile(content.getvalue()) def exif_auto_rotate(image): for orientation in ExifTags.TAGS.keys(): - if ExifTags.TAGS[orientation] == 'Orientation': + if ExifTags.TAGS[orientation] == "Orientation": break exif = dict(image._getexif().items()) @@ -108,54 +121,66 @@ def exif_auto_rotate(image): def doku_to_markdown(text): """This is a quite correct doku translator""" - text = re.sub(r'([^:]|^)\/\/(.*?)\/\/', r'*\2*', text) # Italic (prevents protocol:// conflict) - text = re.sub(r'(.*?)<\/del>', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) - text = re.sub(r'(.*?)<\/sup>', r'^\1^', text) # Superscript (multiline not supported, because almost never used) - text = re.sub(r'(.*?)<\/sub>', r'_\1_', text) # Subscript (idem) + text = re.sub( + r"([^:]|^)\/\/(.*?)\/\/", r"*\2*", text + ) # Italic (prevents protocol:// conflict) + text = re.sub( + r"(.*?)<\/del>", r"~~\1~~", text, flags=re.DOTALL + ) # Strike (may be multiline) + text = re.sub( + r"(.*?)<\/sup>", r"^\1^", text + ) # Superscript (multiline not supported, because almost never used) + text = re.sub(r"(.*?)<\/sub>", r"_\1_", text) # Subscript (idem) - text = re.sub(r'^======(.*?)======', r'#\1', text, flags=re.MULTILINE) # Titles - text = re.sub(r'^=====(.*?)=====', r'##\1', text, flags=re.MULTILINE) - text = re.sub(r'^====(.*?)====', r'###\1', text, flags=re.MULTILINE) - text = re.sub(r'^===(.*?)===', r'####\1', text, flags=re.MULTILINE) - text = re.sub(r'^==(.*?)==', r'#####\1', text, flags=re.MULTILINE) - text = re.sub(r'^=(.*?)=', r'######\1', text, flags=re.MULTILINE) + text = re.sub(r"^======(.*?)======", r"#\1", text, flags=re.MULTILINE) # Titles + text = re.sub(r"^=====(.*?)=====", r"##\1", text, flags=re.MULTILINE) + text = re.sub(r"^====(.*?)====", r"###\1", text, flags=re.MULTILINE) + text = re.sub(r"^===(.*?)===", r"####\1", text, flags=re.MULTILINE) + text = re.sub(r"^==(.*?)==", r"#####\1", text, flags=re.MULTILINE) + text = re.sub(r"^=(.*?)=", r"######\1", text, flags=re.MULTILINE) - text = re.sub(r'', r'', text) - text = re.sub(r'', r'', text) - text = re.sub(r'', r'```\n', text) - text = re.sub(r'', r'\n```', text) - text = re.sub(r'article://', r'page://', text) - text = re.sub(r'dfile://', r'file://', text) + text = re.sub(r"", r"", text) + text = re.sub(r"", r"", text) + text = re.sub(r"", r"```\n", text) + text = re.sub(r"", r"\n```", text) + text = re.sub(r"article://", r"page://", text) + text = re.sub(r"dfile://", r"file://", text) i = 1 - for fn in re.findall(r'\(\((.*?)\)\)', text): # Footnotes - text = re.sub(r'\(\((.*?)\)\)', r'[^%s]' % i, text, count=1) + for fn in re.findall(r"\(\((.*?)\)\)", text): # Footnotes + text = re.sub(r"\(\((.*?)\)\)", r"[^%s]" % i, text, count=1) text += "\n[^%s]: %s\n" % (i, fn) i += 1 - text = re.sub(r'\\{2,}[\s]', r' \n', text) # Carriage return + text = re.sub(r"\\{2,}[\s]", r" \n", text) # Carriage return - text = re.sub(r'\[\[(.*?)\|(.*?)\]\]', r'[\2](\1)', text) # Links - text = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', text) # Links 2 - text = re.sub(r'{{(.*?)\|(.*?)}}', r'![\2](\1 "\2")', text) # Images - text = re.sub(r'{{(.*?)(\|(.*?))?}}', r'![\1](\1 "\1")', text) # Images 2 - text = re.sub(r'{\[(.*?)(\|(.*?))?\]}', r'[\1](\1)', text) # Video (transform to classic links, since we can't integrate them) + text = re.sub(r"\[\[(.*?)\|(.*?)\]\]", r"[\2](\1)", text) # Links + text = re.sub(r"\[\[(.*?)\]\]", r"[\1](\1)", text) # Links 2 + text = re.sub(r"{{(.*?)\|(.*?)}}", r'![\2](\1 "\2")', text) # Images + text = re.sub(r"{{(.*?)(\|(.*?))?}}", r'![\1](\1 "\1")', text) # Images 2 + text = re.sub( + r"{\[(.*?)(\|(.*?))?\]}", r"[\1](\1)", text + ) # Video (transform to classic links, since we can't integrate them) - text = re.sub(r'###(\d*?)###', r'[[[\1]]]', text) # Progress bar + text = re.sub(r"###(\d*?)###", r"[[[\1]]]", text) # Progress bar - text = re.sub(r'(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)', r'```\1\n```', text, flags=re.DOTALL) # Block code without lists + text = re.sub( + r"(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)", r"```\1\n```", text, flags=re.DOTALL + ) # Block code without lists - text = re.sub(r'( +)-(.*)', r'1.\2', text) # Ordered lists + text = re.sub(r"( +)-(.*)", r"1.\2", text) # Ordered lists new_text = [] quote_level = 0 for line in text.splitlines(): # Tables and quotes - enter = re.finditer(r'\[quote(=(.+?))?\]', line) - quit = re.finditer(r'\[/quote\]', line) - if re.search(r'\A\s*\^(([^\^]*?)\^)*', line): # Table part - line = line.replace('^', '|') + enter = re.finditer(r"\[quote(=(.+?))?\]", line) + quit = re.finditer(r"\[/quote\]", line) + if re.search(r"\A\s*\^(([^\^]*?)\^)*", line): # Table part + line = line.replace("^", "|") new_text.append("> " * quote_level + line) - new_text.append("> " * quote_level + "|---|") # Don't keep the text alignement in tables it's really too complex for what it's worth + new_text.append( + "> " * quote_level + "|---|" + ) # Don't keep the text alignement in tables it's really too complex for what it's worth elif enter or quit: # Quote part for quote in enter: # Enter quotes (support multiple at a time) quote_level += 1 @@ -163,16 +188,20 @@ def doku_to_markdown(text): new_text.append("> " * quote_level + "##### " + quote.group(2)) except: new_text.append("> " * quote_level) - line = line.replace(quote.group(0), '') - final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration + line = line.replace(quote.group(0), "") + final_quote_level = ( + quote_level + ) # Store quote_level to use at the end, since it will be modified during quit iteration final_newline = False for quote in quit: # Quit quotes (support multiple at a time) - line = line.replace(quote.group(0), '') + line = line.replace(quote.group(0), "") quote_level -= 1 final_newline = True new_text.append("> " * final_quote_level + line) # Finally append the line if final_newline: - new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text + new_text.append( + "\n" + ) # Add a new line to ensure the separation between the quote and the following text else: new_text.append(line) @@ -181,24 +210,28 @@ def doku_to_markdown(text): def bbcode_to_markdown(text): """This is a very basic BBcode translator""" - text = re.sub(r'\[b\](.*?)\[\/b\]', r'**\1**', text, flags=re.DOTALL) # Bold - text = re.sub(r'\[i\](.*?)\[\/i\]', r'*\1*', text, flags=re.DOTALL) # Italic - text = re.sub(r'\[u\](.*?)\[\/u\]', r'__\1__', text, flags=re.DOTALL) # Underline - text = re.sub(r'\[s\](.*?)\[\/s\]', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) - text = re.sub(r'\[strike\](.*?)\[\/strike\]', r'~~\1~~', text, flags=re.DOTALL) # Strike 2 + text = re.sub(r"\[b\](.*?)\[\/b\]", r"**\1**", text, flags=re.DOTALL) # Bold + text = re.sub(r"\[i\](.*?)\[\/i\]", r"*\1*", text, flags=re.DOTALL) # Italic + text = re.sub(r"\[u\](.*?)\[\/u\]", r"__\1__", text, flags=re.DOTALL) # Underline + text = re.sub( + r"\[s\](.*?)\[\/s\]", r"~~\1~~", text, flags=re.DOTALL + ) # Strike (may be multiline) + text = re.sub( + r"\[strike\](.*?)\[\/strike\]", r"~~\1~~", text, flags=re.DOTALL + ) # Strike 2 - text = re.sub(r'article://', r'page://', text) - text = re.sub(r'dfile://', r'file://', text) + text = re.sub(r"article://", r"page://", text) + text = re.sub(r"dfile://", r"file://", text) - text = re.sub(r'\[url=(.*?)\](.*)\[\/url\]', r'[\2](\1)', text) # Links - text = re.sub(r'\[url\](.*)\[\/url\]', r'\1', text) # Links 2 - text = re.sub(r'\[img\](.*)\[\/img\]', r'![\1](\1 "\1")', text) # Images + text = re.sub(r"\[url=(.*?)\](.*)\[\/url\]", r"[\2](\1)", text) # Links + text = re.sub(r"\[url\](.*)\[\/url\]", r"\1", text) # Links 2 + text = re.sub(r"\[img\](.*)\[\/img\]", r'![\1](\1 "\1")', text) # Images new_text = [] quote_level = 0 for line in text.splitlines(): # Tables and quotes - enter = re.finditer(r'\[quote(=(.+?))?\]', line) - quit = re.finditer(r'\[/quote\]', line) + enter = re.finditer(r"\[quote(=(.+?))?\]", line) + quit = re.finditer(r"\[/quote\]", line) if enter or quit: # Quote part for quote in enter: # Enter quotes (support multiple at a time) quote_level += 1 @@ -206,16 +239,20 @@ def bbcode_to_markdown(text): new_text.append("> " * quote_level + "##### " + quote.group(2)) except: new_text.append("> " * quote_level) - line = line.replace(quote.group(0), '') - final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration + line = line.replace(quote.group(0), "") + final_quote_level = ( + quote_level + ) # Store quote_level to use at the end, since it will be modified during quit iteration final_newline = False for quote in quit: # Quit quotes (support multiple at a time) - line = line.replace(quote.group(0), '') + line = line.replace(quote.group(0), "") quote_level -= 1 final_newline = True new_text.append("> " * final_quote_level + line) # Finally append the line if final_newline: - new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text + new_text.append( + "\n" + ) # Add a new line to ensure the separation between the quote and the following text else: new_text.append(line) diff --git a/core/views/__init__.py b/core/views/__init__.py index c27470f5..7045b7f7 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -26,43 +26,69 @@ import types from django.shortcuts import render from django.http import HttpResponseForbidden, HttpResponseNotFound -from django.core.exceptions import PermissionDenied, ObjectDoesNotExist, ImproperlyConfigured +from django.core.exceptions import ( + PermissionDenied, + ObjectDoesNotExist, + ImproperlyConfigured, +) from django.views.generic.base import View from django.db.models import Count from core.models import Group from core.views.forms import LoginForm + def forbidden(request): try: - return HttpResponseForbidden(render(request, "core/403.jinja", context={'next': request.path, 'form': - LoginForm(), 'popup': request.resolver_match.kwargs['popup'] or ""})) + return HttpResponseForbidden( + render( + request, + "core/403.jinja", + context={ + "next": request.path, + "form": LoginForm(), + "popup": request.resolver_match.kwargs["popup"] or "", + }, + ) + ) except: - return HttpResponseForbidden(render(request, "core/403.jinja", context={'next': request.path, 'form': LoginForm()})) + return HttpResponseForbidden( + render( + request, + "core/403.jinja", + context={"next": request.path, "form": LoginForm()}, + ) + ) + def not_found(request): return HttpResponseNotFound(render(request, "core/404.jinja")) + def can_edit_prop(obj, user): if obj is None or user.is_owner(obj): return True return False + def can_edit(obj, user): if obj is None or user.can_edit(obj): return True return can_edit_prop(obj, user) + def can_view(obj, user): if obj is None or user.can_view(obj): return True return can_edit(obj, user) + class CanCreateMixin(View): """ This view is made to protect any child view that would create an object, and thus, that can not be protected by any of the following mixin """ + def dispatch(self, request, *arg, **kwargs): res = super(CanCreateMixin, self).dispatch(request, *arg, **kwargs) if not request.user.is_authenticated(): @@ -75,6 +101,7 @@ class CanCreateMixin(View): return super(CanCreateMixin, self).form_valid(form) raise PermissionDenied + class CanEditPropMixin(View): """ This view is made to protect any child view that would be showing some properties of an object that are restricted @@ -82,64 +109,78 @@ class CanEditPropMixin(View): In other word, you can make a view with this view as parent, and it would be retricted to the users that are in the object's owner_group """ + def dispatch(self, request, *arg, **kwargs): try: self.object = self.get_object() if can_edit_prop(self.object, request.user): return super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs) return forbidden(request) - except: pass + except: + pass # If we get here, it's a ListView l_id = [o.id for o in self.get_queryset() if can_edit_prop(o, request.user)] if not l_id and self.get_queryset().count() != 0: raise PermissionDenied self._get_queryset = self.get_queryset + def get_qs(self2): return self2._get_queryset().filter(id__in=l_id) + self.get_queryset = types.MethodType(get_qs, self) return super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs) + class CanEditMixin(View): """ This view makes exactly the same thing as its direct parent, but checks the group on the edit_groups field of the object """ + def dispatch(self, request, *arg, **kwargs): try: self.object = self.get_object() if can_edit(self.object, request.user): return super(CanEditMixin, self).dispatch(request, *arg, **kwargs) return forbidden(request) - except: pass + except: + pass # If we get here, it's a ListView l_id = [o.id for o in self.get_queryset() if can_edit(o, request.user)] if not l_id and self.get_queryset().count() != 0: raise PermissionDenied self._get_queryset = self.get_queryset + def get_qs(self2): return self2._get_queryset().filter(id__in=l_id) + self.get_queryset = types.MethodType(get_qs, self) return super(CanEditMixin, self).dispatch(request, *arg, **kwargs) + class CanViewMixin(View): """ This view still makes exactly the same thing as its direct parent, but checks the group on the view_groups field of the object """ + def dispatch(self, request, *arg, **kwargs): try: self.object = self.get_object() if can_view(self.object, request.user): return super(CanViewMixin, self).dispatch(request, *arg, **kwargs) return forbidden(request) - except: pass + except: + pass # If we get here, it's a ListView l_id = [o.id for o in self.get_queryset() if can_view(o, request.user)] if not l_id and self.get_queryset().count() != 0: raise PermissionDenied self._get_queryset = self.get_queryset + def get_qs(self2): return self2._get_queryset().filter(id__in=l_id) + self.get_queryset = types.MethodType(get_qs, self) return super(CanViewMixin, self).dispatch(request, *arg, **kwargs) @@ -148,6 +189,7 @@ class FormerSubscriberMixin(View): """ This view check if the user was at least an old subscriber """ + def dispatch(self, request, *args, **kwargs): if not request.user.was_subscribed: raise PermissionDenied @@ -158,6 +200,7 @@ class TabedViewMixin(View): """ This view provide the basic functions for displaying tabs in the template """ + def get_tabs_title(self): try: return self.tabs_title @@ -178,38 +221,42 @@ class TabedViewMixin(View): def get_context_data(self, **kwargs): kwargs = super(TabedViewMixin, self).get_context_data(**kwargs) - kwargs['tabs_title'] = self.get_tabs_title() - kwargs['current_tab'] = self.get_current_tab() - kwargs['list_of_tabs'] = self.get_list_of_tabs() + kwargs["tabs_title"] = self.get_tabs_title() + kwargs["current_tab"] = self.get_current_tab() + kwargs["list_of_tabs"] = self.get_list_of_tabs() return kwargs + class QuickNotifMixin: quick_notif_list = [] def dispatch(self, request, *arg, **kwargs): - self.quick_notif_list = [] # In some cases, the class can stay instanciated, so we need to reset the list + self.quick_notif_list = ( + [] + ) # In some cases, the class can stay instanciated, so we need to reset the list return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs) def get_success_url(self): ret = super(QuickNotifMixin, self).get_success_url() try: - if '?' in ret: - ret += '&' + self.quick_notif_url_arg + if "?" in ret: + ret += "&" + self.quick_notif_url_arg else: - ret += '?' + self.quick_notif_url_arg - except: pass + ret += "?" + self.quick_notif_url_arg + except: + pass return ret def get_context_data(self, **kwargs): """Add quick notifications to context""" kwargs = super(QuickNotifMixin, self).get_context_data(**kwargs) - kwargs['quick_notifs'] = [] + kwargs["quick_notifs"] = [] for n in self.quick_notif_list: - kwargs['quick_notifs'].append(settings.SITH_QUICK_NOTIF[n]) - for k,v in settings.SITH_QUICK_NOTIF.items(): + kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n]) + for k, v in settings.SITH_QUICK_NOTIF.items(): for gk in self.request.GET.keys(): if k == gk: - kwargs['quick_notifs'].append(v) + kwargs["quick_notifs"].append(v) return kwargs @@ -218,5 +265,3 @@ from .page import * from .files import * from .site import * from .group import * - - diff --git a/core/views/files.py b/core/views/files.py index 5df973be..60902cfe 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -54,55 +54,93 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"): f = file_class.objects.filter(id=file_id).first() if f is None or not f.file: return not_found(request) - if not (can_view(f, request.user) or - ('counter_token' in request.session.keys() and - request.session['counter_token'] and # check if not null for counters that have no token set - Counter.objects.filter(token=request.session['counter_token']).exists()) - ): + if not ( + can_view(f, request.user) + or ( + "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter( # check if not null for counters that have no token set + token=request.session["counter_token"] + ).exists() + ) + ): raise PermissionDenied name = f.__getattribute__(file_attr).name filepath = os.path.join(settings.MEDIA_ROOT, name) - with open(filepath.encode('utf-8'), 'rb') as filename: + with open(filepath.encode("utf-8"), "rb") as filename: wrapper = FileWrapper(filename) response = HttpResponse(wrapper, content_type=f.mime_type) - response['Content-Length'] = os.path.getsize(filepath.encode('utf-8')) - response['Content-Disposition'] = ('inline; filename="%s"' % f.name).encode('utf-8') + response["Content-Length"] = os.path.getsize(filepath.encode("utf-8")) + response["Content-Disposition"] = ('inline; filename="%s"' % f.name).encode( + "utf-8" + ) return response class AddFilesForm(forms.Form): - folder_name = forms.CharField(label=_("Add a new folder"), max_length=30, required=False) - file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Files"), - required=False) + folder_name = forms.CharField( + label=_("Add a new folder"), max_length=30, required=False + ) + file_field = forms.FileField( + widget=forms.ClearableFileInput(attrs={"multiple": True}), + label=_("Files"), + required=False, + ) def process(self, parent, owner, files): notif = False try: - if self.cleaned_data['folder_name'] != "": - folder = SithFile(parent=parent, name=self.cleaned_data['folder_name'], owner=owner) + if self.cleaned_data["folder_name"] != "": + folder = SithFile( + parent=parent, name=self.cleaned_data["folder_name"], owner=owner + ) folder.clean() folder.save() notif = True except Exception as e: - self.add_error(None, _("Error creating folder %(folder_name)s: %(msg)s") % - {'folder_name': self.cleaned_data['folder_name'], 'msg': repr(e)}) + self.add_error( + None, + _("Error creating folder %(folder_name)s: %(msg)s") + % {"folder_name": self.cleaned_data["folder_name"], "msg": repr(e)}, + ) for f in files: - new_file = SithFile(parent=parent, name=f.name, file=f, owner=owner, is_folder=False, - mime_type=f.content_type, size=f._size) + new_file = SithFile( + parent=parent, + name=f.name, + file=f, + owner=owner, + is_folder=False, + mime_type=f.content_type, + size=f._size, + ) try: new_file.clean() new_file.save() notif = True except Exception as e: - self.add_error(None, _("Error uploading file %(file_name)s: %(msg)s") % {'file_name': f, 'msg': repr(e)}) + self.add_error( + None, + _("Error uploading file %(file_name)s: %(msg)s") + % {"file_name": f, "msg": repr(e)}, + ) if notif: - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - if not u.notifications.filter(type="FILE_MODERATION", viewed=False).exists(): - Notification(user=u, url=reverse("core:file_moderation"), type="FILE_MODERATION").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + if not u.notifications.filter( + type="FILE_MODERATION", viewed=False + ).exists(): + Notification( + user=u, + url=reverse("core:file_moderation"), + type="FILE_MODERATION", + ).save() class FileListView(ListView): - template_name = 'core/file_list.jinja' + template_name = "core/file_list.jinja" context_object_name = "file_list" def get_queryset(self): @@ -110,81 +148,94 @@ class FileListView(ListView): def get_context_data(self, **kwargs): kwargs = super(FileListView, self).get_context_data(**kwargs) - kwargs['popup'] = "" - if self.kwargs['popup']: - kwargs['popup'] = 'popup' + kwargs["popup"] = "" + if self.kwargs["popup"]: + kwargs["popup"] = "popup" return kwargs class FileEditView(CanEditMixin, UpdateView): model = SithFile pk_url_kwarg = "file_id" - template_name = 'core/file_edit.jinja' + template_name = "core/file_edit.jinja" context_object_name = "file" def get_form_class(self): - fields = ['name', 'is_moderated'] + fields = ["name", "is_moderated"] if self.object.is_file: - fields = ['file'] + fields + fields = ["file"] + fields return modelform_factory(SithFile, fields=fields) def get_success_url(self): - if self.kwargs['popup']: - return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': "popup"}) - return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': ""}) + if self.kwargs["popup"]: + return reverse( + "core:file_detail", kwargs={"file_id": self.object.id, "popup": "popup"} + ) + return reverse( + "core:file_detail", kwargs={"file_id": self.object.id, "popup": ""} + ) def get_context_data(self, **kwargs): kwargs = super(FileEditView, self).get_context_data(**kwargs) - kwargs['popup'] = "" - if self.kwargs['popup']: - kwargs['popup'] = 'popup' + kwargs["popup"] = "" + if self.kwargs["popup"]: + kwargs["popup"] = "popup" return kwargs class FileEditPropForm(forms.ModelForm): class Meta: model = SithFile - fields = ['parent', 'owner', 'edit_groups', 'view_groups'] - parent = make_ajax_field(SithFile, 'parent', 'files', help_text="") - edit_groups = make_ajax_field(SithFile, 'edit_groups', 'groups', help_text="", label=_("edit group")) - view_groups = make_ajax_field(SithFile, 'view_groups', 'groups', help_text="", label=_("view group")) + fields = ["parent", "owner", "edit_groups", "view_groups"] + + parent = make_ajax_field(SithFile, "parent", "files", help_text="") + edit_groups = make_ajax_field( + SithFile, "edit_groups", "groups", help_text="", label=_("edit group") + ) + view_groups = make_ajax_field( + SithFile, "view_groups", "groups", help_text="", label=_("view group") + ) recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) class FileEditPropView(CanEditPropMixin, UpdateView): model = SithFile pk_url_kwarg = "file_id" - template_name = 'core/file_edit.jinja' + template_name = "core/file_edit.jinja" context_object_name = "file" form_class = FileEditPropForm def get_form(self, form_class=None): form = super(FileEditPropView, self).get_form(form_class) - form.fields['parent'].queryset = SithFile.objects.filter(is_folder=True) + form.fields["parent"].queryset = SithFile.objects.filter(is_folder=True) return form def form_valid(self, form): ret = super(FileEditPropView, self).form_valid(form) - if form.cleaned_data['recursive']: + if form.cleaned_data["recursive"]: self.object.apply_rights_recursively() return ret def get_success_url(self): - return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': self.kwargs['popup'] or ""}) + return reverse( + "core:file_detail", + kwargs={"file_id": self.object.id, "popup": self.kwargs["popup"] or ""}, + ) def get_context_data(self, **kwargs): kwargs = super(FileEditPropView, self).get_context_data(**kwargs) - kwargs['popup'] = "" - if self.kwargs['popup']: - kwargs['popup'] = 'popup' + kwargs["popup"] = "" + if self.kwargs["popup"]: + kwargs["popup"] = "popup" return kwargs class FileView(CanViewMixin, DetailView, FormMixin): """This class handle the upload of new files into a folder""" + model = SithFile pk_url_kwarg = "file_id" - template_name = 'core/file_detail.jinja' + template_name = "core/file_detail.jinja" context_object_name = "file" form_class = AddFilesForm @@ -201,79 +252,99 @@ class FileView(CanViewMixin, DetailView, FormMixin): `object` is the SithFile object you want to put in the clipboard, or where you want to paste the clipboard """ - if 'delete' in request.POST.keys(): - for f_id in request.POST.getlist('file_list'): + if "delete" in request.POST.keys(): + for f_id in request.POST.getlist("file_list"): sf = SithFile.objects.filter(id=f_id).first() if sf: sf.delete() - if 'clear' in request.POST.keys(): - request.session['clipboard'] = [] - if 'cut' in request.POST.keys(): - for f_id in request.POST.getlist('file_list'): + if "clear" in request.POST.keys(): + request.session["clipboard"] = [] + if "cut" in request.POST.keys(): + for f_id in request.POST.getlist("file_list"): f_id = int(f_id) - if f_id in [c.id for c in object.children.all()] and f_id not in request.session['clipboard']: - request.session['clipboard'].append(f_id) - if 'paste' in request.POST.keys(): - for f_id in request.session['clipboard']: + if ( + f_id in [c.id for c in object.children.all()] + and f_id not in request.session["clipboard"] + ): + request.session["clipboard"].append(f_id) + if "paste" in request.POST.keys(): + for f_id in request.session["clipboard"]: sf = SithFile.objects.filter(id=f_id).first() if sf: sf.move_to(object) - request.session['clipboard'] = [] + request.session["clipboard"] = [] request.session.modified = True def get(self, request, *args, **kwargs): self.form = self.get_form() - if 'clipboard' not in request.session.keys(): - request.session['clipboard'] = [] + if "clipboard" not in request.session.keys(): + request.session["clipboard"] = [] return super(FileView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() - if 'clipboard' not in request.session.keys(): - request.session['clipboard'] = [] + if "clipboard" not in request.session.keys(): + request.session["clipboard"] = [] if request.user.can_edit(self.object): # XXX this call can fail! FileView.handle_clipboard(request, self.object) self.form = self.get_form() # The form handle only the file upload - files = request.FILES.getlist('file_field') - if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): + files = request.FILES.getlist("file_field") + if ( + request.user.is_authenticated() + and request.user.can_edit(self.object) + and self.form.is_valid() + ): self.form.process(parent=self.object, owner=request.user, files=files) if self.form.is_valid(): return super(FileView, self).form_valid(self.form) return self.form_invalid(self.form) def get_success_url(self): - return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': self.kwargs['popup'] or ""}) + return reverse( + "core:file_detail", + kwargs={"file_id": self.object.id, "popup": self.kwargs["popup"] or ""}, + ) def get_context_data(self, **kwargs): kwargs = super(FileView, self).get_context_data(**kwargs) - kwargs['popup'] = "" - kwargs['form'] = self.form - if self.kwargs['popup']: - kwargs['popup'] = 'popup' - kwargs['clipboard'] = SithFile.objects.filter(id__in=self.request.session['clipboard']) + kwargs["popup"] = "" + kwargs["form"] = self.form + if self.kwargs["popup"]: + kwargs["popup"] = "popup" + kwargs["clipboard"] = SithFile.objects.filter( + id__in=self.request.session["clipboard"] + ) return kwargs class FileDeleteView(CanEditPropMixin, DeleteView): model = SithFile pk_url_kwarg = "file_id" - template_name = 'core/file_delete_confirm.jinja' + template_name = "core/file_delete_confirm.jinja" context_object_name = "file" def get_success_url(self): self.object.file.delete() # Doing it here or overloading delete() is the same, so let's do it here - if 'next' in self.request.GET.keys(): - return self.request.GET['next'] + if "next" in self.request.GET.keys(): + return self.request.GET["next"] if self.object.parent is None: - return reverse('core:file_list', kwargs={'popup': self.kwargs['popup'] or ""}) - return reverse('core:file_detail', kwargs={'file_id': self.object.parent.id, 'popup': self.kwargs['popup'] or ""}) + return reverse( + "core:file_list", kwargs={"popup": self.kwargs["popup"] or ""} + ) + return reverse( + "core:file_detail", + kwargs={ + "file_id": self.object.parent.id, + "popup": self.kwargs["popup"] or "", + }, + ) def get_context_data(self, **kwargs): kwargs = super(FileDeleteView, self).get_context_data(**kwargs) - kwargs['popup'] = "" - if self.kwargs['popup']: - kwargs['popup'] = 'popup' + kwargs["popup"] = "" + if self.kwargs["popup"]: + kwargs["popup"] = "popup" return kwargs @@ -282,7 +353,7 @@ class FileModerationView(TemplateView): def get_context_data(self, **kwargs): kwargs = super(FileModerationView, self).get_context_data(**kwargs) - kwargs['files'] = SithFile.objects.filter(is_moderated=False)[:100] + kwargs["files"] = SithFile.objects.filter(is_moderated=False)[:100] return kwargs @@ -295,6 +366,6 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin): self.object.is_moderated = True self.object.moderator = request.user self.object.save() - if 'next' in self.request.GET.keys(): - return redirect(self.request.GET['next']) - return redirect('core:file_moderation') + if "next" in self.request.GET.keys(): + return redirect(self.request.GET["next"]) + return redirect("core:file_moderation") diff --git a/core/views/forms.py b/core/views/forms.py index 957bf498..a0290182 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -27,7 +27,14 @@ from django import forms from django.conf import settings from django.db import transaction from django.core.exceptions import ValidationError -from django.forms import CheckboxSelectMultiple, Select, DateInput, TextInput, DateTimeInput, Textarea +from django.forms import ( + CheckboxSelectMultiple, + Select, + DateInput, + TextInput, + DateTimeInput, + Textarea, +) from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget @@ -45,114 +52,144 @@ from PIL import Image # Widgets + class SelectSingle(Select): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_single" + attrs["class"] = "select_single" else: - attrs = {'class': "select_single"} + attrs = {"class": "select_single"} return super(SelectSingle, self).render(name, value, attrs) class SelectMultiple(Select): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_multiple" + attrs["class"] = "select_multiple" else: - attrs = {'class': "select_multiple"} + attrs = {"class": "select_multiple"} return super(SelectMultiple, self).render(name, value, attrs) class SelectDateTime(DateTimeInput): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_datetime" + attrs["class"] = "select_datetime" else: - attrs = {'class': "select_datetime"} + attrs = {"class": "select_datetime"} return super(SelectDateTime, self).render(name, value, attrs) class SelectDate(DateInput): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_date" + attrs["class"] = "select_date" else: - attrs = {'class': "select_date"} + attrs = {"class": "select_date"} return super(SelectDate, self).render(name, value, attrs) class MarkdownInput(Textarea): def render(self, name, value, attrs=None): - output = '

    %(help_text)s

    '\ - '
    %(content)s
    ' % { - 'syntax_url': Page.get_page_by_full_name(settings.SITH_CORE_PAGE_SYNTAX).get_absolute_url(), - 'help_text': _("Help on the syntax"), - 'content': super(MarkdownInput, self).render(name, value, attrs), - } + output = ( + '

    %(help_text)s

    ' + '
    %(content)s
    ' + % { + "syntax_url": Page.get_page_by_full_name( + settings.SITH_CORE_PAGE_SYNTAX + ).get_absolute_url(), + "help_text": _("Help on the syntax"), + "content": super(MarkdownInput, self).render(name, value, attrs), + } + ) return output class SelectFile(TextInput): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_file" + attrs["class"] = "select_file" else: - attrs = {'class': "select_file"} - output = '%(content)s
    ' % { - 'content': super(SelectFile, self).render(name, value, attrs), - 'title': _("Choose file"), - 'name': name, - } - output += '' + ugettext("Choose file") + '' + attrs = {"class": "select_file"} + output = ( + '%(content)s
    ' + % { + "content": super(SelectFile, self).render(name, value, attrs), + "title": _("Choose file"), + "name": name, + } + ) + output += ( + '' + + ugettext("Choose file") + + "" + ) return output class SelectUser(TextInput): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_user" + attrs["class"] = "select_user" else: - attrs = {'class': "select_user"} - output = '%(content)s
    ' % { - 'content': super(SelectUser, self).render(name, value, attrs), - 'title': _("Choose user"), - 'name': name, - } - output += '' + ugettext("Choose user") + '' + attrs = {"class": "select_user"} + output = ( + '%(content)s
    ' + % { + "content": super(SelectUser, self).render(name, value, attrs), + "title": _("Choose user"), + "name": name, + } + ) + output += ( + '' + + ugettext("Choose user") + + "" + ) return output + # Forms class LoginForm(AuthenticationForm): def __init__(self, *arg, **kwargs): - if 'data' in kwargs.keys(): + if "data" in kwargs.keys(): from counter.models import Customer - data = kwargs['data'].copy() + + data = kwargs["data"].copy() account_code = re.compile(r"^[0-9]+[A-Za-z]$") try: - if account_code.match(data['username']): - user = Customer.objects.filter(account_id__iexact=data['username']).first().user - elif '@' in data['username']: - user = User.objects.filter(email__iexact=data['username']).first() + if account_code.match(data["username"]): + user = ( + Customer.objects.filter(account_id__iexact=data["username"]) + .first() + .user + ) + elif "@" in data["username"]: + user = User.objects.filter(email__iexact=data["username"]).first() else: - user = User.objects.filter(username=data['username']).first() - data['username'] = user.username + user = User.objects.filter(username=data["username"]).first() + data["username"] = user.username except: pass - kwargs['data'] = data + kwargs["data"] = data super(LoginForm, self).__init__(*arg, **kwargs) - self.fields['username'].label = _("Username, email, or account number") + self.fields["username"].label = _("Username, email, or account number") class RegisteringForm(UserCreationForm): - error_css_class = 'error' - required_css_class = 'required' + error_css_class = "error" + required_css_class = "required" captcha = CaptchaField() class Meta: model = User - fields = ('first_name', 'last_name', 'email') + fields = ("first_name", "last_name", "email") def save(self, commit=True): user = super(RegisteringForm, self).save(commit=False) @@ -169,24 +206,49 @@ class UserProfileForm(forms.ModelForm): This form is actually pretty bad and was made in the rush before the migration. It should be refactored. TODO: refactor this form """ + class Meta: model = User - fields = ['first_name', 'last_name', 'nick_name', 'email', 'date_of_birth', 'profile_pict', 'avatar_pict', - 'scrub_pict', 'sex', 'second_email', 'address', 'parent_address', 'phone', 'parent_phone', - 'tshirt_size', 'role', 'department', 'dpt_option', 'semester', 'quote', 'school', 'promo', - 'forum_signature', 'is_subscriber_viewable'] + fields = [ + "first_name", + "last_name", + "nick_name", + "email", + "date_of_birth", + "profile_pict", + "avatar_pict", + "scrub_pict", + "sex", + "second_email", + "address", + "parent_address", + "phone", + "parent_phone", + "tshirt_size", + "role", + "department", + "dpt_option", + "semester", + "quote", + "school", + "promo", + "forum_signature", + "is_subscriber_viewable", + ] widgets = { - 'date_of_birth': SelectDate, - 'profile_pict': forms.ClearableFileInput, - 'avatar_pict': forms.ClearableFileInput, - 'scrub_pict': forms.ClearableFileInput, - 'phone': PhoneNumberInternationalFallbackWidget, - 'parent_phone': PhoneNumberInternationalFallbackWidget, + "date_of_birth": SelectDate, + "profile_pict": forms.ClearableFileInput, + "avatar_pict": forms.ClearableFileInput, + "scrub_pict": forms.ClearableFileInput, + "phone": PhoneNumberInternationalFallbackWidget, + "parent_phone": PhoneNumberInternationalFallbackWidget, } labels = { - 'profile_pict': _("Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)"), - 'avatar_pict': _("Avatar: used on the forum"), - 'scrub_pict': _("Scrub: let other know how your scrub looks like!"), + "profile_pict": _( + "Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)" + ), + "avatar_pict": _("Avatar: used on the forum"), + "scrub_pict": _("Scrub: let other know how your scrub looks like!"), } def __init__(self, *arg, **kwargs): @@ -197,27 +259,36 @@ class UserProfileForm(forms.ModelForm): def generate_name(self, field_name, f): field_name = field_name[:-4] - return field_name + str(self.instance.id) + "." + f.content_type.split('/')[-1] + return field_name + str(self.instance.id) + "." + f.content_type.split("/")[-1] def process(self, files): avatar = self.instance.avatar_pict profile = self.instance.profile_pict scrub = self.instance.scrub_pict self.full_clean() - self.cleaned_data['avatar_pict'] = avatar - self.cleaned_data['profile_pict'] = profile - self.cleaned_data['scrub_pict'] = scrub + self.cleaned_data["avatar_pict"] = avatar + self.cleaned_data["profile_pict"] = profile + self.cleaned_data["scrub_pict"] = scrub parent = SithFile.objects.filter(parent=None, name="profiles").first() for field, f in files: with transaction.atomic(): try: im = Image.open(BytesIO(f.read())) - new_file = SithFile(parent=parent, name=self.generate_name(field, f), - file=resize_image(im, 400, f.content_type.split('/')[-1]), - owner=self.instance, is_folder=False, mime_type=f.content_type, size=f._size, - moderator=self.instance, is_moderated=True) + new_file = SithFile( + parent=parent, + name=self.generate_name(field, f), + file=resize_image(im, 400, f.content_type.split("/")[-1]), + owner=self.instance, + is_folder=False, + mime_type=f.content_type, + size=f._size, + moderator=self.instance, + is_moderated=True, + ) new_file.file.name = new_file.name - old = SithFile.objects.filter(parent=parent, name=new_file.name).first() + old = SithFile.objects.filter( + parent=parent, name=new_file.name + ).first() if old: old.delete() new_file.clean() @@ -226,73 +297,101 @@ class UserProfileForm(forms.ModelForm): self._errors.pop(field, None) except ValidationError as e: self._errors.pop(field, None) - self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") % - {'file_name': f, 'msg': str(e.message)}) + self.add_error( + field, + _("Error uploading file %(file_name)s: %(msg)s") + % {"file_name": f, "msg": str(e.message)}, + ) except IOError: self._errors.pop(field, None) - self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") % - {'file_name': f, 'msg': _("Bad image format, only jpeg, png, and gif are accepted")}) + self.add_error( + field, + _("Error uploading file %(file_name)s: %(msg)s") + % { + "file_name": f, + "msg": _( + "Bad image format, only jpeg, png, and gif are accepted" + ), + }, + ) self._post_clean() class UserPropForm(forms.ModelForm): - error_css_class = 'error' - required_css_class = 'required' + error_css_class = "error" + required_css_class = "required" class Meta: model = User - fields = ['groups'] - help_texts = { - 'groups': "Which groups this user belongs to", - } - widgets = { - 'groups': CheckboxSelectMultiple, - } + fields = ["groups"] + help_texts = {"groups": "Which groups this user belongs to"} + widgets = {"groups": CheckboxSelectMultiple} class UserGodfathersForm(forms.Form): - type = forms.ChoiceField(choices=[('godfather', _("Godfather")), ('godchild', _("Godchild"))], label=_("Add")) - user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None) + type = forms.ChoiceField( + choices=[("godfather", _("Godfather")), ("godchild", _("Godchild"))], + label=_("Add"), + ) + user = AutoCompleteSelectField( + "users", required=True, label=_("Select user"), help_text=None + ) class PagePropForm(forms.ModelForm): - error_css_class = 'error' - required_css_class = 'required' + error_css_class = "error" + required_css_class = "required" class Meta: model = Page - fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ] - edit_groups = make_ajax_field(Page, 'edit_groups', 'groups', help_text="", label=_("edit groups")) - view_groups = make_ajax_field(Page, 'view_groups', 'groups', help_text="", label=_("view groups")) + fields = ["parent", "name", "owner_group", "edit_groups", "view_groups"] + + edit_groups = make_ajax_field( + Page, "edit_groups", "groups", help_text="", label=_("edit groups") + ) + view_groups = make_ajax_field( + Page, "view_groups", "groups", help_text="", label=_("view groups") + ) def __init__(self, *arg, **kwargs): super(PagePropForm, self).__init__(*arg, **kwargs) - self.fields['edit_groups'].required = False - self.fields['view_groups'].required = False + self.fields["edit_groups"].required = False + self.fields["view_groups"].required = False class PageForm(forms.ModelForm): class Meta: model = Page - fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups'] - edit_groups = make_ajax_field(Page, 'edit_groups', 'groups', help_text="", label=_("edit groups")) - view_groups = make_ajax_field(Page, 'view_groups', 'groups', help_text="", label=_("view groups")) + fields = ["parent", "name", "owner_group", "edit_groups", "view_groups"] + + edit_groups = make_ajax_field( + Page, "edit_groups", "groups", help_text="", label=_("edit groups") + ) + view_groups = make_ajax_field( + Page, "view_groups", "groups", help_text="", label=_("view groups") + ) def __init__(self, *args, **kwargs): super(PageForm, self).__init__(*args, **kwargs) - self.fields['parent'].queryset = self.fields['parent'].queryset.exclude(name=settings.SITH_CLUB_ROOT_PAGE).filter(club=None) + self.fields["parent"].queryset = ( + self.fields["parent"] + .queryset.exclude(name=settings.SITH_CLUB_ROOT_PAGE) + .filter(club=None) + ) class GiftForm(forms.ModelForm): class Meta: model = Gift - fields = ['label', 'user'] + fields = ["label", "user"] label = forms.ChoiceField(choices=settings.SITH_GIFT_LIST) def __init__(self, *args, **kwargs): - user_id = kwargs.pop('user_id', None) + user_id = kwargs.pop("user_id", None) super(GiftForm, self).__init__(*args, **kwargs) if user_id: - self.fields['user'].queryset = self.fields['user'].queryset.filter(id=user_id) - self.fields['user'].widget = forms.HiddenInput() + self.fields["user"].queryset = self.fields["user"].queryset.filter( + id=user_id + ) + self.fields["user"].widget = forms.HiddenInput() diff --git a/core/views/group.py b/core/views/group.py index 8dc54905..29864dbc 100644 --- a/core/views/group.py +++ b/core/views/group.py @@ -34,6 +34,7 @@ class GroupListView(CanEditMixin, ListView): """ Displays the group list """ + model = RealGroup template_name = "core/group_list.jinja" @@ -42,17 +43,17 @@ class GroupEditView(CanEditMixin, UpdateView): model = RealGroup pk_url_kwarg = "group_id" template_name = "core/group_edit.jinja" - fields = ['name', 'description'] + fields = ["name", "description"] class GroupCreateView(CanEditMixin, CreateView): model = RealGroup template_name = "core/group_edit.jinja" - fields = ['name', 'description'] + fields = ["name", "description"] class GroupDeleteView(CanEditMixin, DeleteView): model = RealGroup pk_url_kwarg = "group_id" template_name = "core/delete_confirm.jinja" - success_url = reverse_lazy('core:group_list') + success_url = reverse_lazy("core:group_list") diff --git a/core/views/page.py b/core/views/page.py index 14d50187..92b9b381 100644 --- a/core/views/page.py +++ b/core/views/page.py @@ -36,7 +36,6 @@ from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMi class CanEditPagePropMixin(CanEditPropMixin): - def dispatch(self, request, *args, **kwargs): res = super(CanEditPagePropMixin, self).dispatch(request, *args, **kwargs) if self.object.is_club_page: @@ -46,93 +45,95 @@ class CanEditPagePropMixin(CanEditPropMixin): class PageListView(CanViewMixin, ListView): model = Page - template_name = 'core/page_list.jinja' + template_name = "core/page_list.jinja" class PageView(CanViewMixin, DetailView): model = Page - template_name = 'core/page_detail.jinja' + template_name = "core/page_detail.jinja" def dispatch(self, request, *args, **kwargs): res = super(PageView, self).dispatch(request, *args, **kwargs) if self.object and self.object.need_club_redirection: - return redirect('club:club_view', club_id=self.object.club.id) + return redirect("club:club_view", club_id=self.object.club.id) return res def get_object(self): - self.page = Page.get_page_by_full_name(self.kwargs['page_name']) + self.page = Page.get_page_by_full_name(self.kwargs["page_name"]) return self.page def get_context_data(self, **kwargs): context = super(PageView, self).get_context_data(**kwargs) if "page" not in context.keys(): - context['new_page'] = self.kwargs['page_name'] + context["new_page"] = self.kwargs["page_name"] return context class PageHistView(CanViewMixin, DetailView): model = Page - template_name = 'core/page_hist.jinja' + template_name = "core/page_hist.jinja" def dispatch(self, request, *args, **kwargs): res = super(PageHistView, self).dispatch(request, *args, **kwargs) if self.object.need_club_redirection: - return redirect('club:club_hist', club_id=self.object.club.id) + return redirect("club:club_hist", club_id=self.object.club.id) return res def get_object(self): - self.page = Page.get_page_by_full_name(self.kwargs['page_name']) + self.page = Page.get_page_by_full_name(self.kwargs["page_name"]) return self.page class PageRevView(CanViewMixin, DetailView): model = Page - template_name = 'core/page_detail.jinja' + template_name = "core/page_detail.jinja" def dispatch(self, request, *args, **kwargs): res = super(PageRevView, self).dispatch(request, *args, **kwargs) if self.object.need_club_redirection: - return redirect('club:club_view_rev', club_id=self.object.club.id, rev_id=kwargs['rev']) + return redirect( + "club:club_view_rev", club_id=self.object.club.id, rev_id=kwargs["rev"] + ) return res def get_object(self): - self.page = Page.get_page_by_full_name(self.kwargs['page_name']) + self.page = Page.get_page_by_full_name(self.kwargs["page_name"]) return self.page def get_context_data(self, **kwargs): context = super(PageRevView, self).get_context_data(**kwargs) if self.page is not None: - context['page'] = self.page + context["page"] = self.page try: - rev = self.page.revisions.get(id=self.kwargs['rev']) - context['rev'] = rev + rev = self.page.revisions.get(id=self.kwargs["rev"]) + context["rev"] = rev except: # By passing, the template will just display the normal page without taking revision into account pass else: - context['new_page'] = self.kwargs['page_name'] + context["new_page"] = self.kwargs["page_name"] return context class PageCreateView(CanCreateMixin, CreateView): model = Page form_class = PageForm - template_name = 'core/page_prop.jinja' + template_name = "core/page_prop.jinja" def get_initial(self): init = {} - if 'page' in self.request.GET.keys(): - page_name = self.request.GET['page'] - parent_name = '/'.join(page_name.split('/')[:-1]) + if "page" in self.request.GET.keys(): + page_name = self.request.GET["page"] + parent_name = "/".join(page_name.split("/")[:-1]) parent = Page.get_page_by_full_name(parent_name) if parent is not None: - init['parent'] = parent.id - init['name'] = page_name.split('/')[-1] + init["parent"] = parent.id + init["name"] = page_name.split("/")[-1] return init def get_context_data(self, **kwargs): context = super(PageCreateView, self).get_context_data(**kwargs) - context['new_page'] = True + context["new_page"] = True return context def form_valid(self, form): @@ -144,9 +145,9 @@ class PageCreateView(CanCreateMixin, CreateView): class PagePropView(CanEditPagePropMixin, UpdateView): model = Page form_class = PagePropForm - template_name = 'core/page_prop.jinja' - slug_field = '_full_name' - slug_url_kwarg = 'page_name' + template_name = "core/page_prop.jinja" + slug_field = "_full_name" + slug_url_kwarg = "page_name" def get_object(self): o = super(PagePropView, self).get_object() @@ -169,11 +170,13 @@ class PagePropView(CanEditPagePropMixin, UpdateView): class PageEditViewBase(CanEditMixin, UpdateView): model = PageRev - form_class = modelform_factory(model=PageRev, fields=['title', 'content', ], widgets={'content': MarkdownInput}) - template_name = 'core/pagerev_edit.jinja' + form_class = modelform_factory( + model=PageRev, fields=["title", "content"], widgets={"content": MarkdownInput} + ) + template_name = "core/pagerev_edit.jinja" def get_object(self): - self.page = Page.get_page_by_full_name(self.kwargs['page_name']) + self.page = Page.get_page_by_full_name(self.kwargs["page_name"]) return self._get_revision() def _get_revision(self): @@ -193,17 +196,15 @@ class PageEditViewBase(CanEditMixin, UpdateView): def get_context_data(self, **kwargs): context = super(PageEditViewBase, self).get_context_data(**kwargs) if self.page is not None: - context['page'] = self.page + context["page"] = self.page else: - context['new_page'] = self.kwargs['page_name'] + context["new_page"] = self.kwargs["page_name"] return context def form_valid(self, form): # TODO : factor that, but first make some tests rev = form.instance - new_rev = PageRev(title=rev.title, - content=rev.content, - ) + new_rev = PageRev(title=rev.title, content=rev.content) new_rev.author = self.request.user new_rev.page = self.page form.instance = new_rev @@ -211,18 +212,17 @@ class PageEditViewBase(CanEditMixin, UpdateView): class PageEditView(PageEditViewBase): - def dispatch(self, request, *args, **kwargs): res = super(PageEditView, self).dispatch(request, *args, **kwargs) if self.object and self.object.page.need_club_redirection: - return redirect('club:club_edit_page', club_id=self.object.page.club.id) + return redirect("club:club_edit_page", club_id=self.object.page.club.id) return res class PageDeleteView(CanEditPagePropMixin, DeleteView): model = Page - template_name = 'core/delete_confirm.jinja' - pk_url_kwarg = 'page_id' + template_name = "core/delete_confirm.jinja" + pk_url_kwarg = "page_id" def get_success_url(self, **kwargs): - return reverse_lazy('core:page_list') + return reverse_lazy("core:page_list") diff --git a/core/views/site.py b/core/views/site.py index a4084e90..55c26608 100644 --- a/core/views/site.py +++ b/core/views/site.py @@ -41,6 +41,7 @@ from club.models import Club def index(request, context=None): if request.user.is_authenticated(): from com.views import NewsListView + return NewsListView.as_view()(request) return render(request, "core/index.jinja") @@ -50,11 +51,11 @@ class NotificationList(ListView): template_name = "core/notification_list.jinja" def get_queryset(self): - if 'see_all' in self.request.GET.keys(): + if "see_all" in self.request.GET.keys(): for n in self.request.user.notifications.all(): n.viewed = True n.save() - return self.request.user.notifications.order_by('-date')[:20] + return self.request.user.notifications.order_by("-date")[:20] def notification(request, notif_id): @@ -70,7 +71,12 @@ def notification(request, notif_id): def search_user(query, as_json=False): - res = SearchQuerySet().models(User).filter(text=query).filter_or(text__contains=query)[:20] + res = ( + SearchQuerySet() + .models(User) + .filter(text=query) + .filter_or(text__contains=query)[:20] + ) return [r.object for r in res] @@ -79,8 +85,10 @@ def search_club(query, as_json=False): if query: clubs = Club.objects.filter(name__icontains=query).all() clubs = clubs[:5] - if as_json: # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers - clubs = json.loads(serializers.serialize('json', clubs, fields=('name'))) + if ( + as_json + ): # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers + clubs = json.loads(serializers.serialize("json", clubs, fields=("name"))) else: clubs = list(clubs) return clubs @@ -89,25 +97,23 @@ def search_club(query, as_json=False): @login_required def search_view(request): result = { - 'users': search_user(request.GET.get('query', '')), - 'clubs': search_club(request.GET.get('query', '')), + "users": search_user(request.GET.get("query", "")), + "clubs": search_club(request.GET.get("query", "")), } - return render(request, "core/search.jinja", context={'result': result}) + return render(request, "core/search.jinja", context={"result": result}) @login_required def search_user_json(request): - result = { - 'users': search_user(request.GET.get('query', ''), True), - } + result = {"users": search_user(request.GET.get("query", ""), True)} return JsonResponse(result) @login_required def search_json(request): result = { - 'users': search_user(request.GET.get('query', ''), True), - 'clubs': search_club(request.GET.get('query', ''), True), + "users": search_user(request.GET.get("query", ""), True), + "clubs": search_club(request.GET.get("query", ""), True), } return JsonResponse(result) @@ -116,8 +122,8 @@ class ToMarkdownView(TemplateView): template_name = "core/to_markdown.jinja" def post(self, request, *args, **kwargs): - self.text = request.POST['text'] - if request.POST['syntax'] == "doku": + self.text = request.POST["text"] + if request.POST["syntax"] == "doku": self.text_md = doku_to_markdown(self.text) else: self.text_md = bbcode_to_markdown(self.text) @@ -127,9 +133,9 @@ class ToMarkdownView(TemplateView): def get_context_data(self, **kwargs): kwargs = super(ToMarkdownView, self).get_context_data(**kwargs) try: - kwargs['text'] = self.text - kwargs['text_md'] = self.text_md + kwargs["text"] = self.text + kwargs["text_md"] = self.text_md except: - kwargs['text'] = "" - kwargs['text_md'] = "" + kwargs["text"] = "" + kwargs["text_md"] = "" return kwargs diff --git a/core/views/user.py b/core/views/user.py index ad441650..fb9e6455 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -31,7 +31,13 @@ from django.core.urlresolvers import reverse from django.core.exceptions import PermissionDenied, ValidationError from django.http import Http404, HttpResponse from django.views.generic.edit import UpdateView -from django.views.generic import ListView, DetailView, TemplateView, CreateView, DeleteView +from django.views.generic import ( + ListView, + DetailView, + TemplateView, + CreateView, + DeleteView, +) from django.forms.models import modelform_factory from django.forms import CheckboxSelectMultiple from django.core.urlresolvers import reverse_lazy @@ -42,8 +48,20 @@ from django.views.generic.dates import YearMixin, MonthMixin from datetime import timedelta, date import logging -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, QuickNotifMixin -from core.views.forms import RegisteringForm, UserProfileForm, LoginForm, UserGodfathersForm, GiftForm +from core.views import ( + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + TabedViewMixin, + QuickNotifMixin, +) +from core.views.forms import ( + RegisteringForm, + UserProfileForm, + LoginForm, + UserGodfathersForm, + GiftForm, +) from core.models import User, SithFile, Preferences, Gift from subscription.models import Subscription from trombi.views import UserTrombiForm @@ -55,7 +73,9 @@ def login(request): Needs to be improve with correct handling of form exceptions """ - return views.login(request, template_name="core/login.jinja", authentication_form=LoginForm) + return views.login( + request, template_name="core/login.jinja", authentication_form=LoginForm + ) def logout(request): @@ -69,14 +89,20 @@ def password_change(request): """ Allows a user to change its password """ - return views.password_change(request, template_name="core/password_change.jinja", post_change_redirect=reverse("core:password_change_done")) + return views.password_change( + request, + template_name="core/password_change.jinja", + post_change_redirect=reverse("core:password_change_done"), + ) def password_change_done(request): """ Allows a user to change its password """ - return views.password_change_done(request, template_name="core/password_change_done.jinja") + return views.password_change_done( + request, template_name="core/password_change_done.jinja" + ) def password_root_change(request, user_id): @@ -95,62 +121,74 @@ def password_root_change(request, user_id): return redirect("core:password_change_done") else: form = views.SetPasswordForm(user=user) - return TemplateResponse(request, "core/password_change.jinja", {'form': form, 'target': user}) + return TemplateResponse( + request, "core/password_change.jinja", {"form": form, "target": user} + ) def password_reset(request): """ Allows someone to enter an email adresse for resetting password """ - return views.password_reset(request, - template_name="core/password_reset.jinja", - email_template_name="core/password_reset_email.jinja", - post_reset_redirect="core:password_reset_done", - ) + return views.password_reset( + request, + template_name="core/password_reset.jinja", + email_template_name="core/password_reset_email.jinja", + post_reset_redirect="core:password_reset_done", + ) def password_reset_done(request): """ Confirm that the reset email has been sent """ - return views.password_reset_done(request, template_name="core/password_reset_done.jinja") + return views.password_reset_done( + request, template_name="core/password_reset_done.jinja" + ) def password_reset_confirm(request, uidb64=None, token=None): """ Provide a reset password formular """ - return views.password_reset_confirm(request, uidb64=uidb64, token=token, - post_reset_redirect="core:password_reset_complete", - template_name="core/password_reset_confirm.jinja", - ) + return views.password_reset_confirm( + request, + uidb64=uidb64, + token=token, + post_reset_redirect="core:password_reset_complete", + template_name="core/password_reset_confirm.jinja", + ) def password_reset_complete(request): """ Confirm the password has sucessfully been reset """ - return views.password_reset_complete(request, - template_name="core/password_reset_complete.jinja", - ) + return views.password_reset_complete( + request, template_name="core/password_reset_complete.jinja" + ) def register(request): context = {} - if request.method == 'POST': + if request.method == "POST": form = RegisteringForm(request.POST) if form.is_valid(): - logging.debug("Registering " + form.cleaned_data['first_name'] + form.cleaned_data['last_name']) + logging.debug( + "Registering " + + form.cleaned_data["first_name"] + + form.cleaned_data["last_name"] + ) u = form.save() - context['user_registered'] = u - context['tests'] = 'TEST_REGISTER_USER_FORM_OK' + context["user_registered"] = u + context["tests"] = "TEST_REGISTER_USER_FORM_OK" form = RegisteringForm() else: - context['error'] = 'Erreur' - context['tests'] = 'TEST_REGISTER_USER_FORM_FAIL' + context["error"] = "Erreur" + context["tests"] = "TEST_REGISTER_USER_FORM_FAIL" else: form = RegisteringForm() - context['form'] = form.as_p() + context["form"] = form.as_p() return render(request, "core/register.jinja", context) @@ -160,65 +198,103 @@ class UserTabsMixin(TabedViewMixin): def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse('core:user_profile', kwargs={'user_id': self.object.id}), - 'slug': 'infos', - 'name': _("Infos"), - }) - tab_list.append({ - 'url': reverse('core:user_godfathers', kwargs={'user_id': self.object.id}), - 'slug': 'godfathers', - 'name': _("Godfathers"), - }) - tab_list.append({ - 'url': reverse('core:user_pictures', kwargs={'user_id': self.object.id}), - 'slug': 'pictures', - 'name': _("Pictures"), - }) + tab_list.append( + { + "url": reverse("core:user_profile", kwargs={"user_id": self.object.id}), + "slug": "infos", + "name": _("Infos"), + } + ) + tab_list.append( + { + "url": reverse( + "core:user_godfathers", kwargs={"user_id": self.object.id} + ), + "slug": "godfathers", + "name": _("Godfathers"), + } + ) + tab_list.append( + { + "url": reverse( + "core:user_pictures", kwargs={"user_id": self.object.id} + ), + "slug": "pictures", + "name": _("Pictures"), + } + ) if self.request.user == self.object: - tab_list.append({ - 'url': reverse('core:user_tools'), - 'slug': 'tools', - 'name': _("Tools"), - }) + tab_list.append( + {"url": reverse("core:user_tools"), "slug": "tools", "name": _("Tools")} + ) if self.request.user.can_edit(self.object): - tab_list.append({ - 'url': reverse('core:user_edit', kwargs={'user_id': self.object.id}), - 'slug': 'edit', - 'name': _("Edit"), - }) - tab_list.append({ - 'url': reverse('core:user_prefs', kwargs={'user_id': self.object.id}), - 'slug': 'prefs', - 'name': _("Preferences"), - }) + tab_list.append( + { + "url": reverse( + "core:user_edit", kwargs={"user_id": self.object.id} + ), + "slug": "edit", + "name": _("Edit"), + } + ) + tab_list.append( + { + "url": reverse( + "core:user_prefs", kwargs={"user_id": self.object.id} + ), + "slug": "prefs", + "name": _("Preferences"), + } + ) if self.request.user.can_view(self.object): - tab_list.append({ - 'url': reverse('core:user_clubs', kwargs={'user_id': self.object.id}), - 'slug': 'clubs', - 'name': _("Clubs"), - }) + tab_list.append( + { + "url": reverse( + "core:user_clubs", kwargs={"user_id": self.object.id} + ), + "slug": "clubs", + "name": _("Clubs"), + } + ) if self.request.user.is_owner(self.object): - tab_list.append({ - 'url': reverse('core:user_groups', kwargs={'user_id': self.object.id}), - 'slug': 'groups', - 'name': _("Groups"), - }) + tab_list.append( + { + "url": reverse( + "core:user_groups", kwargs={"user_id": self.object.id} + ), + "slug": "groups", + "name": _("Groups"), + } + ) try: - if (self.object.customer and (self.object == self.request.user - or self.request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - or self.request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX) - or self.request.user.is_root)): - tab_list.append({ - 'url': reverse('core:user_stats', kwargs={'user_id': self.object.id}), - 'slug': 'stats', - 'name': _("Stats"), - }) - tab_list.append({ - 'url': reverse('core:user_account', kwargs={'user_id': self.object.id}), - 'slug': 'account', - 'name': _("Account") + " (%s €)" % self.object.customer.amount, - }) + if self.object.customer and ( + self.object == self.request.user + or self.request.user.is_in_group( + settings.SITH_GROUP_ACCOUNTING_ADMIN_ID + ) + or self.request.user.is_in_group( + settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX + ) + or self.request.user.is_root + ): + tab_list.append( + { + "url": reverse( + "core:user_stats", kwargs={"user_id": self.object.id} + ), + "slug": "stats", + "name": _("Stats"), + } + ) + tab_list.append( + { + "url": reverse( + "core:user_account", kwargs={"user_id": self.object.id} + ), + "slug": "account", + "name": _("Account") + " (%s €)" % self.object.customer.amount, + } + ) except: pass return tab_list @@ -228,15 +304,18 @@ class UserView(UserTabsMixin, CanViewMixin, DetailView): """ Display a user's profile """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" template_name = "core/user_detail.jinja" - current_tab = 'infos' + current_tab = "infos" def get_context_data(self, **kwargs): kwargs = super(UserView, self).get_context_data(**kwargs) - kwargs['gift_form'] = GiftForm(user_id=self.object.id, initial={'user': self.object}) + kwargs["gift_form"] = GiftForm( + user_id=self.object.id, initial={"user": self.object} + ) return kwargs @@ -244,33 +323,36 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView): """ Display a user's pictures """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" template_name = "core/user_pictures.jinja" - current_tab = 'pictures' + current_tab = "pictures" def get_context_data(self, **kwargs): kwargs = super(UserPicturesView, self).get_context_data(**kwargs) - kwargs['albums'] = [] - kwargs['pictures'] = {} - picture_qs = self.object.pictures.exclude(picture=None).order_by('-picture__parent__date', 'id').select_related('picture__parent') + kwargs["albums"] = [] + kwargs["pictures"] = {} + picture_qs = ( + self.object.pictures.exclude(picture=None) + .order_by("-picture__parent__date", "id") + .select_related("picture__parent") + ) last_album = None for pict_relation in picture_qs: album = pict_relation.picture.parent if album.id != last_album: - kwargs['albums'].append(album) - kwargs['pictures'][album.id] = [] + kwargs["albums"].append(album) + kwargs["pictures"][album.id] = [] last_album = album.id - kwargs['pictures'][album.id].append(pict_relation.picture) + kwargs["pictures"][album.id].append(pict_relation.picture) return kwargs def DeleteUserGodfathers(request, user_id, godfather_id, is_father): user = User.objects.get(id=user_id) - if ((user == request.user) or - request.user.is_root or - request.user.is_board_member): + if (user == request.user) or request.user.is_root or request.user.is_board_member: ud = get_object_or_404(User, id=godfather_id) if is_father == "True": user.godfathers.remove(ud) @@ -278,28 +360,29 @@ def DeleteUserGodfathers(request, user_id, godfather_id, is_father): user.godchildren.remove(ud) else: raise PermissionDenied - return redirect('core:user_godfathers', user_id=user_id) + return redirect("core:user_godfathers", user_id=user_id) class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView): """ Display a user's godfathers """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" template_name = "core/user_godfathers.jinja" - current_tab = 'godfathers' + current_tab = "godfathers" def post(self, request, *args, **kwargs): self.object = self.get_object() self.form = UserGodfathersForm(request.POST) - if self.form.is_valid() and self.form.cleaned_data['user'] != self.object: - if self.form.cleaned_data['type'] == 'godfather': - self.object.godfathers.add(self.form.cleaned_data['user']) + if self.form.is_valid() and self.form.cleaned_data["user"] != self.object: + if self.form.cleaned_data["type"] == "godfather": + self.object.godfathers.add(self.form.cleaned_data["user"]) self.object.save() else: - self.object.godchildren.add(self.form.cleaned_data['user']) + self.object.godchildren.add(self.form.cleaned_data["user"]) self.object.save() self.form = UserGodfathersForm() return super(UserGodfathersView, self).get(request, *args, **kwargs) @@ -307,39 +390,44 @@ class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): kwargs = super(UserGodfathersView, self).get_context_data(**kwargs) try: - kwargs['form'] = self.form + kwargs["form"] = self.form except: - kwargs['form'] = UserGodfathersForm() + kwargs["form"] = UserGodfathersForm() return kwargs + class UserGodfathersTreeView(UserTabsMixin, CanViewMixin, DetailView): """ Display a user's family tree """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" template_name = "core/user_godfathers_tree.jinja" - current_tab = 'godfathers' + current_tab = "godfathers" def get_context_data(self, **kwargs): kwargs = super(UserGodfathersTreeView, self).get_context_data(**kwargs) if "descent" in self.request.GET: - kwargs['param'] = "godchildren" + kwargs["param"] = "godchildren" else: - kwargs['param'] = "godfathers" - kwargs['members_set'] = set() + kwargs["param"] = "godfathers" + kwargs["members_set"] = set() return kwargs + class UserGodfathersTreePictureView(CanViewMixin, DetailView): """ Display a user's tree as a picture """ + model = User pk_url_kwarg = "user_id" def build_complex_graph(self): import pygraphviz as pgv + self.depth = int(self.request.GET.get("depth", 4)) if self.param == "godfathers": self.graph = pgv.AGraph(strict=False, directed=True, rankdir="BT") @@ -349,7 +437,8 @@ class UserGodfathersTreePictureView(CanViewMixin, DetailView): self.level = 1 # Since the tree isn't very deep, we can build it recursively def crawl_family(user): - if self.level > self.depth: return + if self.level > self.depth: + return self.level += 1 for u in user.__getattribute__(self.param).all(): self.graph.add_edge(user.get_short_name(), u.get_short_name()) @@ -357,12 +446,14 @@ class UserGodfathersTreePictureView(CanViewMixin, DetailView): family.add(u) crawl_family(u) self.level -= 1 + self.graph.add_node(self.object.get_short_name()) family.add(self.object) crawl_family(self.object) def build_family_graph(self): import pygraphviz as pgv + self.graph = pgv.AGraph(strict=False, directed=True) self.graph.add_node(self.object.get_short_name()) for u in self.object.godfathers.all(): @@ -384,29 +475,31 @@ class UserGodfathersTreePictureView(CanViewMixin, DetailView): else: self.build_complex_graph() # Pimp the graph before display - self.graph.node_attr['color'] = "lightblue" - self.graph.node_attr['style'] = "filled" + self.graph.node_attr["color"] = "lightblue" + self.graph.node_attr["style"] = "filled" main_node = self.graph.get_node(self.object.get_short_name()) - main_node.attr['color'] = "sandybrown" - main_node.attr['shape'] = "rect" + main_node.attr["color"] = "sandybrown" + main_node.attr["shape"] = "rect" if self.param == "godchildren": - self.graph.graph_attr['label'] = _("Godchildren") + self.graph.graph_attr["label"] = _("Godchildren") elif self.param == "godfathers": - self.graph.graph_attr['label'] = _("Godfathers") + self.graph.graph_attr["label"] = _("Godfathers") else: - self.graph.graph_attr['label'] = _("Family") + self.graph.graph_attr["label"] = _("Family") img = self.graph.draw(format="png", prog="dot") return HttpResponse(img, content_type="image/png") + class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): """ Display a user's stats """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" template_name = "core/user_stats.jinja" - current_tab = 'stats' + current_tab = "stats" def dispatch(self, request, *arg, **kwargs): profile = self.get_object() @@ -414,10 +507,14 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): if not hasattr(profile, "customer"): raise Http404 - if not (profile == request.user - or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX) - or request.user.is_root): + if not ( + profile == request.user + or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) + or request.user.is_in_group( + settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX + ) + or request.user.is_root + ): raise PermissionDenied return super(UserStatsView, self).dispatch(request, *arg, **kwargs) @@ -426,22 +523,71 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): kwargs = super(UserStatsView, self).get_context_data(**kwargs) from counter.models import Counter from django.db.models import Sum + foyer = Counter.objects.filter(name="Foyer").first() mde = Counter.objects.filter(name="MDE").first() gommette = Counter.objects.filter(name="La Gommette").first() semester_start = Subscription.compute_start(d=date.today(), duration=3) - kwargs['total_perm_time'] = sum([p.end - p.start for p in self.object.permanencies.exclude(end=None)], timedelta()) - kwargs['total_foyer_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=foyer).exclude(end=None)], timedelta()) - kwargs['total_mde_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=mde).exclude(end=None)], timedelta()) - kwargs['total_gommette_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=gommette).exclude(end=None)], timedelta()) - kwargs['total_foyer_buyings'] = sum([b.unit_price * b.quantity for b in - self.object.customer.buyings.filter(counter=foyer, date__gte=semester_start)]) - kwargs['total_mde_buyings'] = sum([b.unit_price * b.quantity for b in self.object.customer.buyings.filter(counter=mde, - date__gte=semester_start)]) - kwargs['total_gommette_buyings'] = sum([b.unit_price * b.quantity for b in - self.object.customer.buyings.filter(counter=gommette, date__gte=semester_start)]) - kwargs['top_product'] = self.object.customer.buyings.values('product__name').annotate( - product_sum=Sum('quantity')).exclude(product_sum=None).order_by('-product_sum').all()[:10] + kwargs["total_perm_time"] = sum( + [p.end - p.start for p in self.object.permanencies.exclude(end=None)], + timedelta(), + ) + kwargs["total_foyer_time"] = sum( + [ + p.end - p.start + for p in self.object.permanencies.filter(counter=foyer).exclude( + end=None + ) + ], + timedelta(), + ) + kwargs["total_mde_time"] = sum( + [ + p.end - p.start + for p in self.object.permanencies.filter(counter=mde).exclude(end=None) + ], + timedelta(), + ) + kwargs["total_gommette_time"] = sum( + [ + p.end - p.start + for p in self.object.permanencies.filter(counter=gommette).exclude( + end=None + ) + ], + timedelta(), + ) + kwargs["total_foyer_buyings"] = sum( + [ + b.unit_price * b.quantity + for b in self.object.customer.buyings.filter( + counter=foyer, date__gte=semester_start + ) + ] + ) + kwargs["total_mde_buyings"] = sum( + [ + b.unit_price * b.quantity + for b in self.object.customer.buyings.filter( + counter=mde, date__gte=semester_start + ) + ] + ) + kwargs["total_gommette_buyings"] = sum( + [ + b.unit_price * b.quantity + for b in self.object.customer.buyings.filter( + counter=gommette, date__gte=semester_start + ) + ] + ) + kwargs["top_product"] = ( + self.object.customer.buyings.values("product__name") + .annotate(product_sum=Sum("quantity")) + .exclude(product_sum=None) + .order_by("-product_sum") + .all()[:10] + ) return kwargs @@ -449,6 +595,7 @@ class UserMiniView(CanViewMixin, DetailView): """ Display a user's profile """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" @@ -459,6 +606,7 @@ class UserListView(ListView, CanEditPropMixin): """ Displays the user list """ + model = User template_name = "core/user_list.jinja" @@ -467,6 +615,7 @@ class UserUploadProfilePictView(CanEditMixin, DetailView): """ Handle the upload of the profile picture taken with webcam in navigator """ + model = User pk_url_kwarg = "user_id" template_name = "core/user_edit.jinja" @@ -475,16 +624,23 @@ class UserUploadProfilePictView(CanEditMixin, DetailView): from core.utils import resize_image from io import BytesIO from PIL import Image + self.object = self.get_object() if self.object.profile_pict: raise ValidationError(_("User already has a profile picture")) - f = request.FILES['new_profile_pict'] + f = request.FILES["new_profile_pict"] parent = SithFile.objects.filter(parent=None, name="profiles").first() name = str(self.object.id) + "_profile.jpg" # Webcamejs uploads JPGs im = Image.open(BytesIO(f.read())) - new_file = SithFile(parent=parent, name=name, - file=resize_image(im, 400, f.content_type.split('/')[-1]), - owner=self.object, is_folder=False, mime_type=f.content_type, size=f._size) + new_file = SithFile( + parent=parent, + name=name, + file=resize_image(im, 400, f.content_type.split("/")[-1]), + owner=self.object, + is_folder=False, + mime_type=f.content_type, + size=f._size, + ) new_file.file.name = name new_file.save() self.object.profile_pict = new_file @@ -496,12 +652,13 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView): """ Edit a user's profile """ + model = User pk_url_kwarg = "user_id" template_name = "core/user_edit.jinja" form_class = UserProfileForm current_tab = "edit" - edit_once = ['profile_pict', 'date_of_birth', 'first_name', 'last_name'] + edit_once = ["profile_pict", "date_of_birth", "first_name", "last_name"] board_only = [] def remove_restricted_fields(self, request): @@ -509,7 +666,9 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView): Removes edit_once and board_only fields """ for i in self.edit_once: - if getattr(self.form.instance, i) and not (request.user.is_board_member or request.user.is_root): + if getattr(self.form.instance, i) and not ( + request.user.is_board_member or request.user.is_root + ): self.form.fields.pop(i, None) for i in self.board_only: if not (request.user.is_board_member or request.user.is_root): @@ -527,14 +686,18 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView): self.remove_restricted_fields(request) files = request.FILES.items() self.form.process(files) - if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): + if ( + request.user.is_authenticated() + and request.user.can_edit(self.object) + and self.form.is_valid() + ): return super(UserUpdateProfileView, self).form_valid(self.form) return self.form_invalid(self.form) def get_context_data(self, **kwargs): kwargs = super(UserUpdateProfileView, self).get_context_data(**kwargs) - kwargs['profile'] = self.form.instance - kwargs['form'] = self.form + kwargs["profile"] = self.form.instance + kwargs["form"] = self.form return kwargs @@ -542,6 +705,7 @@ class UserClubView(UserTabsMixin, CanViewMixin, DetailView): """ Display the user's club(s) """ + model = User context_object_name = "profile" pk_url_kwarg = "user_id" @@ -553,28 +717,30 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): """ Edit a user's preferences """ + model = User pk_url_kwarg = "user_id" template_name = "core/user_preferences.jinja" - form_class = modelform_factory(Preferences, fields=['receive_weekmail', - 'notify_on_click', 'notify_on_refill']) + form_class = modelform_factory( + Preferences, fields=["receive_weekmail", "notify_on_click", "notify_on_refill"] + ) context_object_name = "profile" current_tab = "prefs" def get_object(self, queryset=None): - user = get_object_or_404(User, pk=self.kwargs['user_id']) + user = get_object_or_404(User, pk=self.kwargs["user_id"]) return user def get_form_kwargs(self): kwargs = super(UserPreferencesView, self).get_form_kwargs() pref = self.object.preferences - kwargs.update({'instance': pref}) + kwargs.update({"instance": pref}) return kwargs def get_context_data(self, **kwargs): kwargs = super(UserPreferencesView, self).get_context_data(**kwargs) - if not hasattr(self.object, 'trombi_user'): - kwargs['trombi_form'] = UserTrombiForm() + if not hasattr(self.object, "trombi_user"): + kwargs["trombi_form"] = UserTrombiForm() return kwargs @@ -582,11 +748,13 @@ class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView): """ Edit a user's groups """ + model = User pk_url_kwarg = "user_id" template_name = "core/user_group.jinja" - form_class = modelform_factory(User, fields=['groups'], - widgets={'groups': CheckboxSelectMultiple}) + form_class = modelform_factory( + User, fields=["groups"], widgets={"groups": CheckboxSelectMultiple} + ) context_object_name = "profile" current_tab = "groups" @@ -595,16 +763,18 @@ class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView): """ Displays the logged user's tools """ + template_name = "core/user_tools.jinja" current_tab = "tools" def get_context_data(self, **kwargs): self.object = self.request.user from launderette.models import Launderette + kwargs = super(UserToolsView, self).get_context_data(**kwargs) - kwargs['launderettes'] = Launderette.objects.all() - kwargs['profile'] = self.request.user - kwargs['object'] = self.request.user + kwargs["launderettes"] = Launderette.objects.all() + kwargs["profile"] = self.request.user + kwargs["object"] = self.request.user return kwargs @@ -612,16 +782,21 @@ class UserAccountBase(UserTabsMixin, DetailView): """ Base class for UserAccount """ + model = User pk_url_kwarg = "user_id" current_tab = "account" def dispatch(self, request, *arg, **kwargs): # Manually validates the rights res = super(UserAccountBase, self).dispatch(request, *arg, **kwargs) - if (self.object == request.user - or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX) - or request.user.is_root): + if ( + self.object == request.user + or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) + or request.user.is_in_group( + settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX + ) + or request.user.is_root + ): return res raise PermissionDenied @@ -630,24 +805,20 @@ class UserAccountView(UserAccountBase): """ Display a user's account """ + template_name = "core/user_account.jinja" def expense_by_month(self, obj, calc): stats = [] - for year in obj.datetimes('date', 'year', order='DESC'): + for year in obj.datetimes("date", "year", order="DESC"): stats.append([]) i = 0 for month in obj.filter(date__year=year.year).datetimes( - 'date', 'month', order='DESC'): - q = obj.filter( - date__year=month.year, - date__month=month.month - ) - stats[i].append({ - 'sum': sum([calc(p) for p in q]), - 'date': month - }) + "date", "month", order="DESC" + ): + q = obj.filter(date__year=month.year, date__month=month.month) + stats[i].append({"sum": sum([calc(p) for p in q]), "date": month}) i += 1 return stats @@ -659,22 +830,21 @@ class UserAccountView(UserAccountBase): def get_context_data(self, **kwargs): kwargs = super(UserAccountView, self).get_context_data(**kwargs) - kwargs['profile'] = self.object + kwargs["profile"] = self.object try: - kwargs['customer'] = self.object.customer - kwargs['buyings_month'] = self.expense_by_month( - self.object.customer.buyings, - (lambda q: q.unit_price * q.quantity) + kwargs["customer"] = self.object.customer + kwargs["buyings_month"] = self.expense_by_month( + self.object.customer.buyings, (lambda q: q.unit_price * q.quantity) ) - kwargs['invoices_month'] = self.expense_by_month( - self.object.customer.user.invoices, - self.invoices_calc + kwargs["invoices_month"] = self.expense_by_month( + self.object.customer.user.invoices, self.invoices_calc ) - kwargs['refilling_month'] = self.expense_by_month( - self.object.customer.refillings, - (lambda q: q.amount) + kwargs["refilling_month"] = self.expense_by_month( + self.object.customer.refillings, (lambda q: q.amount) ) - kwargs['etickets'] = self.object.customer.buyings.exclude(product__eticket=None).all() + kwargs["etickets"] = self.object.customer.buyings.exclude( + product__eticket=None + ).all() except Exception as e: print(repr(e)) return kwargs @@ -684,51 +854,52 @@ class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin): """ Display a user's account for month """ + template_name = "core/user_account_detail.jinja" def get_context_data(self, **kwargs): kwargs = super(UserAccountDetailView, self).get_context_data(**kwargs) - kwargs['profile'] = self.object - kwargs['year'] = self.get_year() - kwargs['month'] = self.get_month() + kwargs["profile"] = self.object + kwargs["year"] = self.get_year() + kwargs["month"] = self.get_month() try: - kwargs['customer'] = self.object.customer + kwargs["customer"] = self.object.customer except: pass - kwargs['tab'] = "account" + kwargs["tab"] = "account" return kwargs class GiftCreateView(CreateView): form_class = GiftForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def dispatch(self, request, *args, **kwargs): if not (request.user.is_board_member or request.user.is_root): raise PermissionDenied - self.user = get_object_or_404(User, pk=kwargs['user_id']) + self.user = get_object_or_404(User, pk=kwargs["user_id"]) return super(GiftCreateView, self).dispatch(request, *args, **kwargs) def get_initial(self): - return {'user': self.user} + return {"user": self.user} def get_form_kwargs(self): kwargs = super(GiftCreateView, self).get_form_kwargs() - kwargs['user_id'] = self.user.id + kwargs["user_id"] = self.user.id return kwargs def get_success_url(self): - return reverse_lazy('core:user_profile', kwargs={'user_id': self.user.id}) + return reverse_lazy("core:user_profile", kwargs={"user_id": self.user.id}) class GiftDeleteView(CanEditPropMixin, DeleteView): model = Gift pk_url_kwarg = "gift_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def dispatch(self, request, *args, **kwargs): - self.user = get_object_or_404(User, pk=kwargs['user_id']) + self.user = get_object_or_404(User, pk=kwargs["user_id"]) return super(GiftDeleteView, self).dispatch(request, *args, **kwargs) def get_success_url(self): - return reverse_lazy('core:user_profile', kwargs={'user_id': self.user.id}) + return reverse_lazy("core:user_profile", kwargs={"user_id": self.user.id}) diff --git a/counter/__init__.py b/counter/__init__.py index 0a9419f8..0ace29c4 100644 --- a/counter/__init__.py +++ b/counter/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/counter/migrations/0001_initial.py b/counter/migrations/0001_initial.py index 0051def2..06f01d59 100644 --- a/counter/migrations/0001_initial.py +++ b/counter/migrations/0001_initial.py @@ -10,140 +10,400 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('subscription', '0001_initial'), + ("subscription", "0001_initial"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('core', '0001_initial'), - ('club', '0002_auto_20160824_2152'), + ("core", "0001_initial"), + ("club", "0002_auto_20160824_2152"), ] operations = [ migrations.CreateModel( - name='Counter', + name="Counter", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('type', models.CharField(choices=[('BAR', 'Bar'), ('OFFICE', 'Office'), ('EBOUTIC', 'Eboutic')], max_length=255, verbose_name='counter type')), - ('club', models.ForeignKey(to='club.Club', related_name='counters')), - ('edit_groups', models.ManyToManyField(to='core.Group', blank=True, related_name='editable_counters')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "type", + models.CharField( + choices=[ + ("BAR", "Bar"), + ("OFFICE", "Office"), + ("EBOUTIC", "Eboutic"), + ], + max_length=255, + verbose_name="counter type", + ), + ), + ("club", models.ForeignKey(to="club.Club", related_name="counters")), + ( + "edit_groups", + models.ManyToManyField( + to="core.Group", blank=True, related_name="editable_counters" + ), + ), + ], + options={"verbose_name": "counter"}, + ), + migrations.CreateModel( + name="Customer", + fields=[ + ( + "user", + models.OneToOneField( + primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL + ), + ), + ( + "account_id", + models.CharField( + unique=True, max_length=10, verbose_name="account id" + ), + ), + ( + "amount", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="amount" + ), + ), ], options={ - 'verbose_name': 'counter', + "verbose_name": "customer", + "ordering": ["account_id"], + "verbose_name_plural": "customers", }, ), migrations.CreateModel( - name='Customer', + name="Permanency", fields=[ - ('user', models.OneToOneField(primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), - ('account_id', models.CharField(unique=True, max_length=10, verbose_name='account id')), - ('amount', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='amount')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("start", models.DateTimeField(verbose_name="start date")), + ("end", models.DateTimeField(verbose_name="end date")), + ( + "counter", + models.ForeignKey( + to="counter.Counter", related_name="permanencies" + ), + ), + ( + "user", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, related_name="permanencies" + ), + ), ], - options={ - 'verbose_name': 'customer', - 'ordering': ['account_id'], - 'verbose_name_plural': 'customers', - }, + options={"verbose_name": "permanency"}, ), migrations.CreateModel( - name='Permanency', + name="Product", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('start', models.DateTimeField(verbose_name='start date')), - ('end', models.DateTimeField(verbose_name='end date')), - ('counter', models.ForeignKey(to='counter.Counter', related_name='permanencies')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='permanencies')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "description", + models.TextField(blank=True, verbose_name="description"), + ), + ( + "code", + models.CharField(max_length=16, blank=True, verbose_name="code"), + ), + ( + "purchase_price", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="purchase price" + ), + ), + ( + "selling_price", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="selling price" + ), + ), + ( + "special_selling_price", + accounting.models.CurrencyField( + decimal_places=2, + max_digits=12, + verbose_name="special selling price", + ), + ), + ( + "icon", + models.ImageField( + upload_to="products", null=True, verbose_name="icon", blank=True + ), + ), + ("limit_age", models.IntegerField(default=0, verbose_name="limit age")), + ("tray", models.BooleanField(verbose_name="tray price", default=False)), + ( + "buying_groups", + models.ManyToManyField( + related_name="products", + to="core.Group", + verbose_name="buying groups", + ), + ), + ( + "club", + models.ForeignKey( + verbose_name="club", to="club.Club", related_name="products" + ), + ), + ( + "parent_product", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="children_products", + verbose_name="parent product", + to="counter.Product", + blank=True, + ), + ), ], - options={ - 'verbose_name': 'permanency', - }, + options={"verbose_name": "product"}, ), migrations.CreateModel( - name='Product', + name="ProductType", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('description', models.TextField(blank=True, verbose_name='description')), - ('code', models.CharField(max_length=16, blank=True, verbose_name='code')), - ('purchase_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='purchase price')), - ('selling_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='selling price')), - ('special_selling_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='special selling price')), - ('icon', models.ImageField(upload_to='products', null=True, verbose_name='icon', blank=True)), - ('limit_age', models.IntegerField(default=0, verbose_name='limit age')), - ('tray', models.BooleanField(verbose_name='tray price', default=False)), - ('buying_groups', models.ManyToManyField(related_name='products', to='core.Group', verbose_name='buying groups')), - ('club', models.ForeignKey(verbose_name='club', to='club.Club', related_name='products')), - ('parent_product', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='children_products', verbose_name='parent product', to='counter.Product', blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "description", + models.TextField(null=True, verbose_name="description", blank=True), + ), + ( + "icon", + models.ImageField(upload_to="products", null=True, blank=True), + ), ], - options={ - 'verbose_name': 'product', - }, + options={"verbose_name": "product type"}, ), migrations.CreateModel( - name='ProductType', + name="Refilling", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('description', models.TextField(null=True, verbose_name='description', blank=True)), - ('icon', models.ImageField(upload_to='products', null=True, blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "amount", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="amount" + ), + ), + ("date", models.DateTimeField(verbose_name="date")), + ( + "payment_method", + models.CharField( + choices=[ + ("CHECK", "Check"), + ("CASH", "Cash"), + ("CARD", "Credit card"), + ], + max_length=255, + default="CASH", + verbose_name="payment method", + ), + ), + ( + "bank", + models.CharField( + choices=[ + ("OTHER", "Autre"), + ("SOCIETE-GENERALE", "Société générale"), + ("BANQUE-POPULAIRE", "Banque populaire"), + ("BNP", "BNP"), + ("CAISSE-EPARGNE", "Caisse d'épargne"), + ("CIC", "CIC"), + ("CREDIT-AGRICOLE", "Crédit Agricole"), + ("CREDIT-MUTUEL", "Credit Mutuel"), + ("CREDIT-LYONNAIS", "Credit Lyonnais"), + ("LA-POSTE", "La Poste"), + ], + max_length=255, + default="OTHER", + verbose_name="bank", + ), + ), + ( + "is_validated", + models.BooleanField(verbose_name="is validated", default=False), + ), + ( + "counter", + models.ForeignKey(to="counter.Counter", related_name="refillings"), + ), + ( + "customer", + models.ForeignKey(to="counter.Customer", related_name="refillings"), + ), + ( + "operator", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, + related_name="refillings_as_operator", + ), + ), ], - options={ - 'verbose_name': 'product type', - }, + options={"verbose_name": "refilling"}, ), migrations.CreateModel( - name='Refilling', + name="Selling", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('amount', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='amount')), - ('date', models.DateTimeField(verbose_name='date')), - ('payment_method', models.CharField(choices=[('CHECK', 'Check'), ('CASH', 'Cash'), ('CARD', 'Credit card')], max_length=255, default='CASH', verbose_name='payment method')), - ('bank', models.CharField(choices=[('OTHER', 'Autre'), ('SOCIETE-GENERALE', 'Société générale'), ('BANQUE-POPULAIRE', 'Banque populaire'), ('BNP', 'BNP'), ('CAISSE-EPARGNE', "Caisse d'épargne"), ('CIC', 'CIC'), ('CREDIT-AGRICOLE', 'Crédit Agricole'), ('CREDIT-MUTUEL', 'Credit Mutuel'), ('CREDIT-LYONNAIS', 'Credit Lyonnais'), ('LA-POSTE', 'La Poste')], max_length=255, default='OTHER', verbose_name='bank')), - ('is_validated', models.BooleanField(verbose_name='is validated', default=False)), - ('counter', models.ForeignKey(to='counter.Counter', related_name='refillings')), - ('customer', models.ForeignKey(to='counter.Customer', related_name='refillings')), - ('operator', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='refillings_as_operator')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("label", models.CharField(max_length=64, verbose_name="label")), + ( + "unit_price", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="unit price" + ), + ), + ("quantity", models.IntegerField(verbose_name="quantity")), + ("date", models.DateTimeField(verbose_name="date")), + ( + "payment_method", + models.CharField( + choices=[ + ("SITH_ACCOUNT", "Sith account"), + ("CARD", "Credit card"), + ], + max_length=255, + default="SITH_ACCOUNT", + verbose_name="payment method", + ), + ), + ( + "is_validated", + models.BooleanField(verbose_name="is validated", default=False), + ), + ( + "club", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to="club.Club", + related_name="sellings", + ), + ), + ( + "counter", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to="counter.Counter", + related_name="sellings", + ), + ), + ( + "customer", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to="counter.Customer", + related_name="buyings", + ), + ), + ( + "product", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to="counter.Product", + related_name="sellings", + blank=True, + ), + ), + ( + "seller", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to=settings.AUTH_USER_MODEL, + related_name="sellings_as_operator", + ), + ), ], - options={ - 'verbose_name': 'refilling', - }, - ), - migrations.CreateModel( - name='Selling', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('label', models.CharField(max_length=64, verbose_name='label')), - ('unit_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='unit price')), - ('quantity', models.IntegerField(verbose_name='quantity')), - ('date', models.DateTimeField(verbose_name='date')), - ('payment_method', models.CharField(choices=[('SITH_ACCOUNT', 'Sith account'), ('CARD', 'Credit card')], max_length=255, default='SITH_ACCOUNT', verbose_name='payment method')), - ('is_validated', models.BooleanField(verbose_name='is validated', default=False)), - ('club', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, to='club.Club', related_name='sellings')), - ('counter', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, to='counter.Counter', related_name='sellings')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, to='counter.Customer', related_name='buyings')), - ('product', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, to='counter.Product', related_name='sellings', blank=True)), - ('seller', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, to=settings.AUTH_USER_MODEL, related_name='sellings_as_operator')), - ], - options={ - 'verbose_name': 'selling', - }, + options={"verbose_name": "selling"}, ), migrations.AddField( - model_name='product', - name='product_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='products', verbose_name='product type', to='counter.ProductType', blank=True), + model_name="product", + name="product_type", + field=models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="products", + verbose_name="product type", + to="counter.ProductType", + blank=True, + ), ), migrations.AddField( - model_name='counter', - name='products', - field=models.ManyToManyField(to='counter.Product', blank=True, related_name='counters'), + model_name="counter", + name="products", + field=models.ManyToManyField( + to="counter.Product", blank=True, related_name="counters" + ), ), migrations.AddField( - model_name='counter', - name='sellers', - field=models.ManyToManyField(related_name='counters', to='core.User', blank=True, verbose_name='sellers'), + model_name="counter", + name="sellers", + field=models.ManyToManyField( + related_name="counters", + to="core.User", + blank=True, + verbose_name="sellers", + ), ), migrations.AddField( - model_name='counter', - name='view_groups', - field=models.ManyToManyField(to='core.Group', blank=True, related_name='viewable_counters'), + model_name="counter", + name="view_groups", + field=models.ManyToManyField( + to="core.Group", blank=True, related_name="viewable_counters" + ), ), ] diff --git a/counter/migrations/0002_auto_20160826_1342.py b/counter/migrations/0002_auto_20160826_1342.py index 9bcd4906..721f5c8e 100644 --- a/counter/migrations/0002_auto_20160826_1342.py +++ b/counter/migrations/0002_auto_20160826_1342.py @@ -10,45 +10,94 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('counter', '0001_initial'), + ("counter", "0001_initial"), ] operations = [ migrations.CreateModel( - name='CashRegisterSummary', + name="CashRegisterSummary", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('date', models.DateTimeField(verbose_name='date')), - ('comment', models.TextField(null=True, verbose_name='comment', blank=True)), - ('emptied', models.BooleanField(default=False, verbose_name='emptied')), - ('counter', models.ForeignKey(to='counter.Counter', related_name='cash_summaries', verbose_name='counter')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='cash_summaries', verbose_name='user')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("date", models.DateTimeField(verbose_name="date")), + ( + "comment", + models.TextField(null=True, verbose_name="comment", blank=True), + ), + ("emptied", models.BooleanField(default=False, verbose_name="emptied")), + ( + "counter", + models.ForeignKey( + to="counter.Counter", + related_name="cash_summaries", + verbose_name="counter", + ), + ), + ( + "user", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, + related_name="cash_summaries", + verbose_name="user", + ), + ), ], - options={ - 'verbose_name': 'cash register summary', - }, + options={"verbose_name": "cash register summary"}, ), migrations.CreateModel( - name='CashRegisterSummaryItem', + name="CashRegisterSummaryItem", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('value', accounting.models.CurrencyField(max_digits=12, verbose_name='value', decimal_places=2)), - ('quantity', models.IntegerField(default=0, verbose_name='quantity')), - ('check', models.BooleanField(default=False, verbose_name='check')), - ('cash_summary', models.ForeignKey(to='counter.CashRegisterSummary', related_name='items', verbose_name='cash summary')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ( + "value", + accounting.models.CurrencyField( + max_digits=12, verbose_name="value", decimal_places=2 + ), + ), + ("quantity", models.IntegerField(default=0, verbose_name="quantity")), + ("check", models.BooleanField(default=False, verbose_name="check")), + ( + "cash_summary", + models.ForeignKey( + to="counter.CashRegisterSummary", + related_name="items", + verbose_name="cash summary", + ), + ), ], - options={ - 'verbose_name': 'cash register summary item', - }, + options={"verbose_name": "cash register summary item"}, ), migrations.AlterField( - model_name='permanency', - name='counter', - field=models.ForeignKey(to='counter.Counter', related_name='permanencies', verbose_name='counter'), + model_name="permanency", + name="counter", + field=models.ForeignKey( + to="counter.Counter", + related_name="permanencies", + verbose_name="counter", + ), ), migrations.AlterField( - model_name='permanency', - name='user', - field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='permanencies', verbose_name='user'), + model_name="permanency", + name="user", + field=models.ForeignKey( + to=settings.AUTH_USER_MODEL, + related_name="permanencies", + verbose_name="user", + ), ), ] diff --git a/counter/migrations/0003_permanency_activity.py b/counter/migrations/0003_permanency_activity.py index 21a14717..05707b20 100644 --- a/counter/migrations/0003_permanency_activity.py +++ b/counter/migrations/0003_permanency_activity.py @@ -8,15 +8,17 @@ from django.utils.timezone import utc class Migration(migrations.Migration): - dependencies = [ - ('counter', '0002_auto_20160826_1342'), - ] + dependencies = [("counter", "0002_auto_20160826_1342")] operations = [ migrations.AddField( - model_name='permanency', - name='activity', - field=models.DateTimeField(verbose_name='activity time', auto_now=True, default=datetime.datetime(2016, 8, 26, 17, 5, 31, 202824, tzinfo=utc)), + model_name="permanency", + name="activity", + field=models.DateTimeField( + verbose_name="activity time", + auto_now=True, + default=datetime.datetime(2016, 8, 26, 17, 5, 31, 202824, tzinfo=utc), + ), preserve_default=False, - ), + ) ] diff --git a/counter/migrations/0004_auto_20160826_1907.py b/counter/migrations/0004_auto_20160826_1907.py index 94cc1e1a..6e2ddd21 100644 --- a/counter/migrations/0004_auto_20160826_1907.py +++ b/counter/migrations/0004_auto_20160826_1907.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0003_permanency_activity'), - ] + dependencies = [("counter", "0003_permanency_activity")] operations = [ migrations.AlterField( - model_name='permanency', - name='end', - field=models.DateTimeField(verbose_name='end date', null=True), - ), + model_name="permanency", + name="end", + field=models.DateTimeField(verbose_name="end date", null=True), + ) ] diff --git a/counter/migrations/0005_auto_20160826_2330.py b/counter/migrations/0005_auto_20160826_2330.py index dd8a34e5..4fa5ebea 100644 --- a/counter/migrations/0005_auto_20160826_2330.py +++ b/counter/migrations/0005_auto_20160826_2330.py @@ -6,24 +6,31 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0004_auto_20160826_1907'), - ] + dependencies = [("counter", "0004_auto_20160826_1907")] operations = [ migrations.AlterField( - model_name='counter', - name='club', - field=models.ForeignKey(verbose_name='club', to='club.Club', related_name='counters'), + model_name="counter", + name="club", + field=models.ForeignKey( + verbose_name="club", to="club.Club", related_name="counters" + ), ), migrations.AlterField( - model_name='counter', - name='products', - field=models.ManyToManyField(blank=True, related_name='counters', to='counter.Product', verbose_name='products'), + model_name="counter", + name="products", + field=models.ManyToManyField( + blank=True, + related_name="counters", + to="counter.Product", + verbose_name="products", + ), ), migrations.AlterField( - model_name='permanency', - name='activity', - field=models.DateTimeField(auto_now=True, verbose_name='last activity date'), + model_name="permanency", + name="activity", + field=models.DateTimeField( + auto_now=True, verbose_name="last activity date" + ), ), ] diff --git a/counter/migrations/0006_auto_20160831_1304.py b/counter/migrations/0006_auto_20160831_1304.py index 3db83884..15b7b169 100644 --- a/counter/migrations/0006_auto_20160831_1304.py +++ b/counter/migrations/0006_auto_20160831_1304.py @@ -6,14 +6,17 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0005_auto_20160826_2330'), - ] + dependencies = [("counter", "0005_auto_20160826_2330")] operations = [ migrations.AlterField( - model_name='product', - name='buying_groups', - field=models.ManyToManyField(related_name='products', verbose_name='buying groups', blank=True, to='core.Group'), - ), + model_name="product", + name="buying_groups", + field=models.ManyToManyField( + related_name="products", + verbose_name="buying groups", + blank=True, + to="core.Group", + ), + ) ] diff --git a/counter/migrations/0007_product_archived.py b/counter/migrations/0007_product_archived.py index 918e3268..521eda48 100644 --- a/counter/migrations/0007_product_archived.py +++ b/counter/migrations/0007_product_archived.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0006_auto_20160831_1304'), - ] + dependencies = [("counter", "0006_auto_20160831_1304")] operations = [ migrations.AddField( - model_name='product', - name='archived', - field=models.BooleanField(verbose_name='archived', default=False), - ), + model_name="product", + name="archived", + field=models.BooleanField(verbose_name="archived", default=False), + ) ] diff --git a/counter/migrations/0008_counter_token.py b/counter/migrations/0008_counter_token.py index 28398fed..a02b5a5d 100644 --- a/counter/migrations/0008_counter_token.py +++ b/counter/migrations/0008_counter_token.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0007_product_archived'), - ] + dependencies = [("counter", "0007_product_archived")] operations = [ migrations.AddField( - model_name='counter', - name='token', - field=models.CharField(blank=True, max_length=30, verbose_name='token', null=True), - ), + model_name="counter", + name="token", + field=models.CharField( + blank=True, max_length=30, verbose_name="token", null=True + ), + ) ] diff --git a/counter/migrations/0009_eticket.py b/counter/migrations/0009_eticket.py index b59038b4..6df0ea73 100644 --- a/counter/migrations/0009_eticket.py +++ b/counter/migrations/0009_eticket.py @@ -6,18 +6,37 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0008_counter_token'), - ] + dependencies = [("counter", "0008_counter_token")] operations = [ migrations.CreateModel( - name='Eticket', + name="Eticket", fields=[ - ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), - ('banner', models.ImageField(null=True, upload_to='etickets', blank=True)), - ('secret', models.CharField(unique=True, verbose_name='secret', max_length=64)), - ('product', models.OneToOneField(verbose_name='product', related_name='eticket', to='counter.Product')), + ( + "id", + models.AutoField( + verbose_name="ID", + auto_created=True, + primary_key=True, + serialize=False, + ), + ), + ( + "banner", + models.ImageField(null=True, upload_to="etickets", blank=True), + ), + ( + "secret", + models.CharField(unique=True, verbose_name="secret", max_length=64), + ), + ( + "product", + models.OneToOneField( + verbose_name="product", + related_name="eticket", + to="counter.Product", + ), + ), ], - ), + ) ] diff --git a/counter/migrations/0010_auto_20161003_1900.py b/counter/migrations/0010_auto_20161003_1900.py index 9f1d73c4..e438b05e 100644 --- a/counter/migrations/0010_auto_20161003_1900.py +++ b/counter/migrations/0010_auto_20161003_1900.py @@ -6,19 +6,19 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0009_eticket'), - ] + dependencies = [("counter", "0009_eticket")] operations = [ migrations.AddField( - model_name='eticket', - name='event_date', - field=models.DateField(blank=True, verbose_name='event date', null=True), + model_name="eticket", + name="event_date", + field=models.DateField(blank=True, verbose_name="event date", null=True), ), migrations.AddField( - model_name='eticket', - name='event_title', - field=models.CharField(blank=True, max_length=64, verbose_name='event title', null=True), + model_name="eticket", + name="event_title", + field=models.CharField( + blank=True, max_length=64, verbose_name="event title", null=True + ), ), ] diff --git a/counter/migrations/0011_auto_20161004_2039.py b/counter/migrations/0011_auto_20161004_2039.py index 3ce88403..c70bad63 100644 --- a/counter/migrations/0011_auto_20161004_2039.py +++ b/counter/migrations/0011_auto_20161004_2039.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0010_auto_20161003_1900'), - ] + dependencies = [("counter", "0010_auto_20161003_1900")] operations = [ migrations.AlterField( - model_name='eticket', - name='banner', - field=models.ImageField(null=True, verbose_name='banner', blank=True, upload_to='etickets'), - ), + model_name="eticket", + name="banner", + field=models.ImageField( + null=True, verbose_name="banner", blank=True, upload_to="etickets" + ), + ) ] diff --git a/counter/migrations/0012_auto_20170515_2202.py b/counter/migrations/0012_auto_20170515_2202.py index 8c2ead76..e50d4856 100644 --- a/counter/migrations/0012_auto_20170515_2202.py +++ b/counter/migrations/0012_auto_20170515_2202.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0011_auto_20161004_2039'), - ] + dependencies = [("counter", "0011_auto_20161004_2039")] operations = [ migrations.AlterField( - model_name='permanency', - name='end', - field=models.DateTimeField(db_index=True, verbose_name='end date', null=True), - ), + model_name="permanency", + name="end", + field=models.DateTimeField( + db_index=True, verbose_name="end date", null=True + ), + ) ] diff --git a/counter/migrations/0013_customer_recorded_products.py b/counter/migrations/0013_customer_recorded_products.py index 026b7b33..84f5caf5 100644 --- a/counter/migrations/0013_customer_recorded_products.py +++ b/counter/migrations/0013_customer_recorded_products.py @@ -12,7 +12,9 @@ from counter.models import Customer, Product, Selling, Counter def balance_ecocups(apps, schema_editor): for customer in Customer.objects.all(): customer.recorded_products = 0 - for selling in customer.buyings.filter(product__id__in=[settings.SITH_ECOCUP_CONS, settings.SITH_ECOCUP_DECO]).all(): + for selling in customer.buyings.filter( + product__id__in=[settings.SITH_ECOCUP_CONS, settings.SITH_ECOCUP_DECO] + ).all(): if selling.product.is_record_product: customer.recorded_products += selling.quantity elif selling.product.is_unrecord_product: @@ -20,24 +22,29 @@ def balance_ecocups(apps, schema_editor): if customer.recorded_products < -settings.SITH_ECOCUP_LIMIT: qt = -(customer.recorded_products + settings.SITH_ECOCUP_LIMIT) cons = Product.objects.get(id=settings.SITH_ECOCUP_CONS) - Selling(label=_("Ecocup regularization"), product=cons, unit_price=cons.selling_price, - club=cons.club, counter=Counter.objects.filter(name='Foyer').first(), - quantity=qt, seller=User.objects.get(id=0), customer=customer).save(allow_negative=True) + Selling( + label=_("Ecocup regularization"), + product=cons, + unit_price=cons.selling_price, + club=cons.club, + counter=Counter.objects.filter(name="Foyer").first(), + quantity=qt, + seller=User.objects.get(id=0), + customer=customer, + ).save(allow_negative=True) customer.recorded_products += qt customer.save() class Migration(migrations.Migration): - dependencies = [ - ('counter', '0012_auto_20170515_2202'), - ] + dependencies = [("counter", "0012_auto_20170515_2202")] operations = [ migrations.AddField( - model_name='customer', - name='recorded_products', - field=models.IntegerField(verbose_name='recorded items', default=0), + model_name="customer", + name="recorded_products", + field=models.IntegerField(verbose_name="recorded items", default=0), ), migrations.RunPython(balance_ecocups), ] diff --git a/counter/migrations/0014_auto_20170816_1521.py b/counter/migrations/0014_auto_20170816_1521.py index d7a64ff3..7e13c170 100644 --- a/counter/migrations/0014_auto_20170816_1521.py +++ b/counter/migrations/0014_auto_20170816_1521.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0013_customer_recorded_products'), - ] + dependencies = [("counter", "0013_customer_recorded_products")] operations = [ migrations.AlterField( - model_name='customer', - name='recorded_products', - field=models.IntegerField(default=0, verbose_name='recorded product'), - ), + model_name="customer", + name="recorded_products", + field=models.IntegerField(default=0, verbose_name="recorded product"), + ) ] diff --git a/counter/migrations/0014_auto_20170817_1537.py b/counter/migrations/0014_auto_20170817_1537.py index 8e4e3df5..67acbc13 100644 --- a/counter/migrations/0014_auto_20170817_1537.py +++ b/counter/migrations/0014_auto_20170817_1537.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0013_customer_recorded_products'), - ] + dependencies = [("counter", "0013_customer_recorded_products")] operations = [ migrations.AlterField( - model_name='customer', - name='recorded_products', - field=models.IntegerField(verbose_name='recorded product', default=0), - ), + model_name="customer", + name="recorded_products", + field=models.IntegerField(verbose_name="recorded product", default=0), + ) ] diff --git a/counter/migrations/0015_merge.py b/counter/migrations/0015_merge.py index 23830568..aefcfaca 100644 --- a/counter/migrations/0015_merge.py +++ b/counter/migrations/0015_merge.py @@ -7,9 +7,8 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('counter', '0014_auto_20170817_1537'), - ('counter', '0014_auto_20170816_1521'), + ("counter", "0014_auto_20170817_1537"), + ("counter", "0014_auto_20170816_1521"), ] - operations = [ - ] + operations = [] diff --git a/counter/migrations/0016_producttype_comment.py b/counter/migrations/0016_producttype_comment.py index 26244fe6..295553b2 100644 --- a/counter/migrations/0016_producttype_comment.py +++ b/counter/migrations/0016_producttype_comment.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0015_merge'), - ] + dependencies = [("counter", "0015_merge")] operations = [ migrations.AddField( - model_name='producttype', - name='comment', - field=models.TextField(verbose_name='comment', blank=True, null=True), - ), + model_name="producttype", + name="comment", + field=models.TextField(verbose_name="comment", blank=True, null=True), + ) ] diff --git a/counter/models.py b/counter/models.py index 2c2ebf06..9767cfd3 100644 --- a/counter/models.py +++ b/counter/models.py @@ -48,15 +48,16 @@ class Customer(models.Model): This class extends a user to make a customer. It adds some basic customers informations, such as the accound ID, and is used by other accounting classes as reference to the customer, rather than using User """ + user = models.OneToOneField(User, primary_key=True) - account_id = models.CharField(_('account id'), max_length=10, unique=True) - amount = CurrencyField(_('amount')) - recorded_products = models.IntegerField(_('recorded product'), default=0) + account_id = models.CharField(_("account id"), max_length=10, unique=True) + amount = CurrencyField(_("amount")) + recorded_products = models.IntegerField(_("recorded product"), default=0) class Meta: - verbose_name = _('customer') - verbose_name_plural = _('customers') - ordering = ['account_id', ] + verbose_name = _("customer") + verbose_name_plural = _("customers") + ordering = ["account_id"] def __str__(self): return "%s - %s" % (self.user.username, self.account_id) @@ -70,8 +71,12 @@ class Customer(models.Model): @property def can_buy(self): - return (self.user.subscriptions.last() and - (date.today() - self.user.subscriptions.order_by('subscription_end').last().subscription_end) < timedelta(days=90)) + return self.user.subscriptions.last() and ( + date.today() + - self.user.subscriptions.order_by("subscription_end") + .last() + .subscription_end + ) < timedelta(days=90) def generate_account_id(number): number = str(number) @@ -99,10 +104,10 @@ class Customer(models.Model): self.save() def get_absolute_url(self): - return reverse('core:user_account', kwargs={'user_id': self.user.pk}) + return reverse("core:user_account", kwargs={"user_id": self.user.pk}) def get_full_url(self): - return ''.join(['https://', settings.SITH_URL, self.get_absolute_url()]) + return "".join(["https://", settings.SITH_URL, self.get_absolute_url()]) class ProductType(models.Model): @@ -110,13 +115,14 @@ class ProductType(models.Model): This describes a product type Useful only for categorizing, changes are made at the product level for now """ - name = models.CharField(_('name'), max_length=30) - description = models.TextField(_('description'), null=True, blank=True) - comment = models.TextField(_('comment'), null=True, blank=True) - icon = models.ImageField(upload_to='products', null=True, blank=True) + + name = models.CharField(_("name"), max_length=30) + description = models.TextField(_("description"), null=True, blank=True) + comment = models.TextField(_("comment"), null=True, blank=True) + icon = models.ImageField(upload_to="products", null=True, blank=True) class Meta: - verbose_name = _('product type') + verbose_name = _("product type") def is_owned_by(self, user): """ @@ -130,32 +136,49 @@ class ProductType(models.Model): return self.name def get_absolute_url(self): - return reverse('counter:producttype_list') + return reverse("counter:producttype_list") class Product(models.Model): """ This describes a product, with all its related informations """ - name = models.CharField(_('name'), max_length=64) - description = models.TextField(_('description'), blank=True) - product_type = models.ForeignKey(ProductType, related_name='products', verbose_name=_("product type"), null=True, blank=True, - on_delete=models.SET_NULL) - code = models.CharField(_('code'), max_length=16, blank=True) - purchase_price = CurrencyField(_('purchase price')) - selling_price = CurrencyField(_('selling price')) - special_selling_price = CurrencyField(_('special selling price')) - icon = models.ImageField(upload_to='products', null=True, blank=True, verbose_name=_("icon")) + + name = models.CharField(_("name"), max_length=64) + description = models.TextField(_("description"), blank=True) + product_type = models.ForeignKey( + ProductType, + related_name="products", + verbose_name=_("product type"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + code = models.CharField(_("code"), max_length=16, blank=True) + purchase_price = CurrencyField(_("purchase price")) + selling_price = CurrencyField(_("selling price")) + special_selling_price = CurrencyField(_("special selling price")) + icon = models.ImageField( + upload_to="products", null=True, blank=True, verbose_name=_("icon") + ) club = models.ForeignKey(Club, related_name="products", verbose_name=_("club")) - limit_age = models.IntegerField(_('limit age'), default=0) - tray = models.BooleanField(_('tray price'), default=False) - parent_product = models.ForeignKey('self', related_name='children_products', verbose_name=_("parent product"), null=True, - blank=True, on_delete=models.SET_NULL) - buying_groups = models.ManyToManyField(Group, related_name='products', verbose_name=_("buying groups"), blank=True) + limit_age = models.IntegerField(_("limit age"), default=0) + tray = models.BooleanField(_("tray price"), default=False) + parent_product = models.ForeignKey( + "self", + related_name="children_products", + verbose_name=_("parent product"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + buying_groups = models.ManyToManyField( + Group, related_name="products", verbose_name=_("buying groups"), blank=True + ) archived = models.BooleanField(_("archived"), default=False) class Meta: - verbose_name = _('product') + verbose_name = _("product") @property def is_record_product(self): @@ -169,7 +192,9 @@ class Product(models.Model): """ Method to see if that object can be edited by the given user """ - if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID): + if user.is_in_group( + settings.SITH_GROUP_ACCOUNTING_ADMIN_ID + ) or user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID): return True return False @@ -177,27 +202,39 @@ class Product(models.Model): return "%s (%s)" % (self.name, self.code) def get_absolute_url(self): - return reverse('counter:product_list') + return reverse("counter:product_list") class Counter(models.Model): - name = models.CharField(_('name'), max_length=30) + name = models.CharField(_("name"), max_length=30) club = models.ForeignKey(Club, related_name="counters", verbose_name=_("club")) - products = models.ManyToManyField(Product, related_name="counters", verbose_name=_("products"), blank=True) - type = models.CharField(_('counter type'), - max_length=255, - choices=[('BAR', _('Bar')), ('OFFICE', _('Office')), ('EBOUTIC', _('Eboutic'))]) - sellers = models.ManyToManyField(User, verbose_name=_('sellers'), related_name='counters', blank=True) - edit_groups = models.ManyToManyField(Group, related_name="editable_counters", blank=True) - view_groups = models.ManyToManyField(Group, related_name="viewable_counters", blank=True) - token = models.CharField(_('token'), max_length=30, null=True, blank=True) + products = models.ManyToManyField( + Product, related_name="counters", verbose_name=_("products"), blank=True + ) + type = models.CharField( + _("counter type"), + max_length=255, + choices=[("BAR", _("Bar")), ("OFFICE", _("Office")), ("EBOUTIC", _("Eboutic"))], + ) + sellers = models.ManyToManyField( + User, verbose_name=_("sellers"), related_name="counters", blank=True + ) + edit_groups = models.ManyToManyField( + Group, related_name="editable_counters", blank=True + ) + view_groups = models.ManyToManyField( + Group, related_name="viewable_counters", blank=True + ) + token = models.CharField(_("token"), max_length=30, null=True, blank=True) class Meta: - verbose_name = _('counter') + verbose_name = _("counter") def __getattribute__(self, name): if name == "edit_groups": - return Group.objects.filter(name=self.club.unix_name + settings.SITH_BOARD_SUFFIX).all() + return Group.objects.filter( + name=self.club.unix_name + settings.SITH_BOARD_SUFFIX + ).all() return object.__getattribute__(self, name) def __str__(self): @@ -205,8 +242,8 @@ class Counter(models.Model): def get_absolute_url(self): if self.type == "EBOUTIC": - return reverse('eboutic:main') - return reverse('counter:details', kwargs={'counter_id': self.id}) + return reverse("eboutic:main") + return reverse("counter:details", kwargs={"counter_id": self.id}) def is_owned_by(self, user): mem = self.club.get_membership_for(user) @@ -217,11 +254,16 @@ class Counter(models.Model): def can_be_viewed_by(self, user): if self.type == "BAR": return True - return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user in self.sellers.all() + return ( + user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) + or user in self.sellers.all() + ) def gen_token(self): """Generate a new token for this counter""" - self.token = ''.join(random.choice(string.ascii_letters + string.digits) for x in range(30)) + self.token = "".join( + random.choice(string.ascii_letters + string.digits) for x in range(30) + ) self.save() def add_barman(self, user): @@ -253,7 +295,9 @@ class Counter(models.Model): pl = Permanency.objects.filter(counter=self, end=None).all() bl = [] for p in pl: - if timezone.now() - p.activity < timedelta(minutes=settings.SITH_BARMAN_TIMEOUT): + if timezone.now() - p.activity < timedelta( + minutes=settings.SITH_BARMAN_TIMEOUT + ): bl.append(p.user) else: p.end = p.activity @@ -281,7 +325,10 @@ class Counter(models.Model): """ Returns True if the counter self is inactive from SITH_COUNTER_MINUTE_INACTIVE's value minutes, else False """ - return self.is_open() and ((timezone.now() - self.permanencies.order_by('-activity').first().activity) > datetime.timedelta(minutes=settings.SITH_COUNTER_MINUTE_INACTIVE)) + return self.is_open() and ( + (timezone.now() - self.permanencies.order_by("-activity").first().activity) + > datetime.timedelta(minutes=settings.SITH_COUNTER_MINUTE_INACTIVE) + ) def barman_list(self): """ @@ -294,22 +341,33 @@ class Refilling(models.Model): """ Handle the refilling """ + counter = models.ForeignKey(Counter, related_name="refillings", blank=False) - amount = CurrencyField(_('amount')) - operator = models.ForeignKey(User, related_name="refillings_as_operator", blank=False) + amount = CurrencyField(_("amount")) + operator = models.ForeignKey( + User, related_name="refillings_as_operator", blank=False + ) customer = models.ForeignKey(Customer, related_name="refillings", blank=False) - date = models.DateTimeField(_('date')) - payment_method = models.CharField(_('payment method'), max_length=255, - choices=settings.SITH_COUNTER_PAYMENT_METHOD, default='CASH') - bank = models.CharField(_('bank'), max_length=255, - choices=settings.SITH_COUNTER_BANK, default='OTHER') - is_validated = models.BooleanField(_('is validated'), default=False) + date = models.DateTimeField(_("date")) + payment_method = models.CharField( + _("payment method"), + max_length=255, + choices=settings.SITH_COUNTER_PAYMENT_METHOD, + default="CASH", + ) + bank = models.CharField( + _("bank"), max_length=255, choices=settings.SITH_COUNTER_BANK, default="OTHER" + ) + is_validated = models.BooleanField(_("is validated"), default=False) class Meta: verbose_name = _("refilling") def __str__(self): - return "Refilling: %.2f for %s" % (self.amount, self.customer.user.get_display_name()) + return "Refilling: %.2f for %s" % ( + self.amount, + self.customer.user.get_display_name(), + ) def is_owned_by(self, user): return user.is_owner(self.counter) and self.payment_method != "CARD" @@ -328,11 +386,19 @@ class Refilling(models.Model): self.customer.save() self.is_validated = True if self.customer.user.preferences.notify_on_refill: - Notification(user=self.customer.user, url=reverse('core:user_account_detail', - kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}), - param=str(self.amount), - type="REFILLING", - ).save() + Notification( + user=self.customer.user, + url=reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.customer.user.id, + "year": self.date.year, + "month": self.date.month, + }, + ), + param=str(self.amount), + type="REFILLING", + ).save() super(Refilling, self).save(*args, **kwargs) @@ -340,25 +406,60 @@ class Selling(models.Model): """ Handle the sellings """ + label = models.CharField(_("label"), max_length=64) - product = models.ForeignKey(Product, related_name="sellings", null=True, blank=True, on_delete=models.SET_NULL) - counter = models.ForeignKey(Counter, related_name="sellings", null=True, blank=False, on_delete=models.SET_NULL) - club = models.ForeignKey(Club, related_name="sellings", null=True, blank=False, on_delete=models.SET_NULL) - unit_price = CurrencyField(_('unit price')) - quantity = models.IntegerField(_('quantity')) - seller = models.ForeignKey(User, related_name="sellings_as_operator", null=True, blank=False, on_delete=models.SET_NULL) - customer = models.ForeignKey(Customer, related_name="buyings", null=True, blank=False, on_delete=models.SET_NULL) - date = models.DateTimeField(_('date')) - payment_method = models.CharField(_('payment method'), max_length=255, - choices=[('SITH_ACCOUNT', _('Sith account')), ('CARD', _('Credit card'))], default='SITH_ACCOUNT') - is_validated = models.BooleanField(_('is validated'), default=False) + product = models.ForeignKey( + Product, + related_name="sellings", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + counter = models.ForeignKey( + Counter, + related_name="sellings", + null=True, + blank=False, + on_delete=models.SET_NULL, + ) + club = models.ForeignKey( + Club, related_name="sellings", null=True, blank=False, on_delete=models.SET_NULL + ) + unit_price = CurrencyField(_("unit price")) + quantity = models.IntegerField(_("quantity")) + seller = models.ForeignKey( + User, + related_name="sellings_as_operator", + null=True, + blank=False, + on_delete=models.SET_NULL, + ) + customer = models.ForeignKey( + Customer, + related_name="buyings", + null=True, + blank=False, + on_delete=models.SET_NULL, + ) + date = models.DateTimeField(_("date")) + payment_method = models.CharField( + _("payment method"), + max_length=255, + choices=[("SITH_ACCOUNT", _("Sith account")), ("CARD", _("Credit card"))], + default="SITH_ACCOUNT", + ) + is_validated = models.BooleanField(_("is validated"), default=False) class Meta: verbose_name = _("selling") def __str__(self): - return "Selling: %d x %s (%f) for %s" % (self.quantity, self.label, - self.quantity * self.unit_price, self.customer.user.get_display_name()) + return "Selling: %d x %s (%f) for %s" % ( + self.quantity, + self.label, + self.quantity * self.unit_price, + self.customer.user.get_display_name(), + ) def is_owned_by(self, user): return user.is_owner(self.counter) and self.payment_method != "CARD" @@ -374,30 +475,25 @@ class Selling(models.Model): def send_mail_customer(self): event = self.product.eticket.event_title or _("Unknown event") - subject = _('Eticket bought for the event %(event)s') % {'event': event} + subject = _("Eticket bought for the event %(event)s") % {"event": event} message_html = _( "You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s." ) % { - 'event': event, - 'url': ''.join(( - '', - self.customer.get_full_url(), - '' - )) + "event": event, + "url": "".join( + ( + '', + self.customer.get_full_url(), + "", + ) + ), } message_txt = _( "You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s." - ) % { - 'event': event, - 'url': self.customer.get_full_url(), - } - self.customer.user.email_user( - subject, - message_txt, - html_message=message_html - ) + ) % {"event": event, "url": self.customer.get_full_url()} + self.customer.user.email_user(subject, message_txt, html_message=message_html) def save(self, allow_negative=False, *args, **kwargs): """ @@ -412,34 +508,52 @@ class Selling(models.Model): self.is_validated = True u = User.objects.filter(id=self.customer.user.id).first() if u.was_subscribed: - if self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER: + if ( + self.product + and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER + ): sub = Subscription( member=u, - subscription_type='un-semestre', + subscription_type="un-semestre", payment_method="EBOUTIC", location="EBOUTIC", ) sub.subscription_start = Subscription.compute_start() sub.subscription_start = Subscription.compute_start( - duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration']) + duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type][ + "duration" + ] + ) sub.subscription_end = Subscription.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'], - start=sub.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type][ + "duration" + ], + start=sub.subscription_start, + ) sub.save() - elif self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS: + elif ( + self.product + and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS + ): u = User.objects.filter(id=self.customer.user.id).first() sub = Subscription( member=u, - subscription_type='deux-semestres', + subscription_type="deux-semestres", payment_method="EBOUTIC", location="EBOUTIC", ) sub.subscription_start = Subscription.compute_start() sub.subscription_start = Subscription.compute_start( - duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration']) + duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type][ + "duration" + ] + ) sub.subscription_end = Subscription.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'], - start=sub.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type][ + "duration" + ], + start=sub.subscription_start, + ) sub.save() try: if self.product.eticket: @@ -449,8 +563,14 @@ class Selling(models.Model): if self.customer.user.preferences.notify_on_click: Notification( user=self.customer.user, - url=reverse('core:user_account_detail', - kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}), + url=reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.customer.user.id, + "year": self.date.year, + "month": self.date.month, + }, + ), param="%d x %s" % (self.quantity, self.label), type="SELLING", ).save() @@ -461,29 +581,38 @@ class Permanency(models.Model): """ This class aims at storing a traceability of who was barman where and when """ + user = models.ForeignKey(User, related_name="permanencies", verbose_name=_("user")) - counter = models.ForeignKey(Counter, related_name="permanencies", verbose_name=_("counter")) - start = models.DateTimeField(_('start date')) - end = models.DateTimeField(_('end date'), null=True, db_index=True) - activity = models.DateTimeField(_('last activity date'), auto_now=True) + counter = models.ForeignKey( + Counter, related_name="permanencies", verbose_name=_("counter") + ) + start = models.DateTimeField(_("start date")) + end = models.DateTimeField(_("end date"), null=True, db_index=True) + activity = models.DateTimeField(_("last activity date"), auto_now=True) class Meta: verbose_name = _("permanency") def __str__(self): - return "%s in %s from %s (last activity: %s) to %s" % (self.user, self.counter, - self.start.strftime("%Y-%m-%d %H:%M:%S"), - self.activity.strftime("%Y-%m-%d %H:%M:%S"), - self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "", - ) + return "%s in %s from %s (last activity: %s) to %s" % ( + self.user, + self.counter, + self.start.strftime("%Y-%m-%d %H:%M:%S"), + self.activity.strftime("%Y-%m-%d %H:%M:%S"), + self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "", + ) class CashRegisterSummary(models.Model): - user = models.ForeignKey(User, related_name="cash_summaries", verbose_name=_("user")) - counter = models.ForeignKey(Counter, related_name="cash_summaries", verbose_name=_("counter")) - date = models.DateTimeField(_('date')) - comment = models.TextField(_('comment'), null=True, blank=True) - emptied = models.BooleanField(_('emptied'), default=False) + user = models.ForeignKey( + User, related_name="cash_summaries", verbose_name=_("user") + ) + counter = models.ForeignKey( + Counter, related_name="cash_summaries", verbose_name=_("counter") + ) + date = models.DateTimeField(_("date")) + comment = models.TextField(_("comment"), null=True, blank=True) + emptied = models.BooleanField(_("emptied"), default=False) class Meta: verbose_name = _("cash register summary") @@ -492,37 +621,37 @@ class CashRegisterSummary(models.Model): return "At %s by %s - Total: %s €" % (self.counter, self.user, self.get_total()) def __getattribute__(self, name): - if name[:5] == 'check': - checks = self.items.filter(check=True).order_by('value').all() - if name == 'ten_cents': + if name[:5] == "check": + checks = self.items.filter(check=True).order_by("value").all() + if name == "ten_cents": return self.items.filter(value=0.1, check=False).first() - elif name == 'twenty_cents': + elif name == "twenty_cents": return self.items.filter(value=0.2, check=False).first() - elif name == 'fifty_cents': + elif name == "fifty_cents": return self.items.filter(value=0.5, check=False).first() - elif name == 'one_euro': + elif name == "one_euro": return self.items.filter(value=1, check=False).first() - elif name == 'two_euros': + elif name == "two_euros": return self.items.filter(value=2, check=False).first() - elif name == 'five_euros': + elif name == "five_euros": return self.items.filter(value=5, check=False).first() - elif name == 'ten_euros': + elif name == "ten_euros": return self.items.filter(value=10, check=False).first() - elif name == 'twenty_euros': + elif name == "twenty_euros": return self.items.filter(value=20, check=False).first() - elif name == 'fifty_euros': + elif name == "fifty_euros": return self.items.filter(value=50, check=False).first() - elif name == 'hundred_euros': + elif name == "hundred_euros": return self.items.filter(value=100, check=False).first() - elif name == 'check_1': + elif name == "check_1": return checks[0] if 0 < len(checks) else None - elif name == 'check_2': + elif name == "check_2": return checks[1] if 1 < len(checks) else None - elif name == 'check_3': + elif name == "check_3": return checks[2] if 2 < len(checks) else None - elif name == 'check_4': + elif name == "check_4": return checks[3] if 3 < len(checks) else None - elif name == 'check_5': + elif name == "check_5": return checks[4] if 4 < len(checks) else None else: return object.__getattribute__(self, name) @@ -547,14 +676,16 @@ class CashRegisterSummary(models.Model): return super(CashRegisterSummary, self).save(*args, **kwargs) def get_absolute_url(self): - return reverse('counter:cash_summary_list') + return reverse("counter:cash_summary_list") class CashRegisterSummaryItem(models.Model): - cash_summary = models.ForeignKey(CashRegisterSummary, related_name="items", verbose_name=_("cash summary")) + cash_summary = models.ForeignKey( + CashRegisterSummary, related_name="items", verbose_name=_("cash summary") + ) value = CurrencyField(_("value")) - quantity = models.IntegerField(_('quantity'), default=0) - check = models.BooleanField(_('check'), default=False) + quantity = models.IntegerField(_("quantity"), default=0) + check = models.BooleanField(_("check"), default=False) class Meta: verbose_name = _("cash register summary item") @@ -564,17 +695,24 @@ class Eticket(models.Model): """ Eticket can be linked to a product an allows PDF generation """ - product = models.OneToOneField(Product, related_name='eticket', verbose_name=_("product")) - banner = models.ImageField(upload_to='etickets', null=True, blank=True, verbose_name=_("banner")) - event_date = models.DateField(_('event date'), null=True, blank=True) - event_title = models.CharField(_('event title'), max_length=64, null=True, blank=True) - secret = models.CharField(_('secret'), max_length=64, unique=True) + + product = models.OneToOneField( + Product, related_name="eticket", verbose_name=_("product") + ) + banner = models.ImageField( + upload_to="etickets", null=True, blank=True, verbose_name=_("banner") + ) + event_date = models.DateField(_("event date"), null=True, blank=True) + event_title = models.CharField( + _("event title"), max_length=64, null=True, blank=True + ) + secret = models.CharField(_("secret"), max_length=64, unique=True) def __str__(self): return "%s" % (self.product.name) def get_absolute_url(self): - return reverse('counter:eticket_list') + return reverse("counter:eticket_list") def save(self, *args, **kwargs): if not self.id: @@ -590,4 +728,7 @@ class Eticket(models.Model): def get_hash(self, string): import hashlib import hmac - return hmac.new(bytes(self.secret, 'utf-8'), bytes(string, 'utf-8'), hashlib.sha1).hexdigest() + + return hmac.new( + bytes(self.secret, "utf-8"), bytes(string, "utf-8"), hashlib.sha1 + ).hexdigest() diff --git a/counter/tests.py b/counter/tests.py index 15fcc45b..7e037826 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -39,46 +39,48 @@ class CounterTest(TestCase): self.mde = Counter.objects.filter(name="MDE").first() def test_full_click(self): - response = self.client.post(reverse("counter:login", kwargs={"counter_id": self.mde.id}), { - "username": self.skia.username, - "password": "plop" - }) + response = self.client.post( + reverse("counter:login", kwargs={"counter_id": self.mde.id}), + {"username": self.skia.username, "password": "plop"}, + ) response = self.client.get( - reverse("counter:details", kwargs={"counter_id": self.mde.id})) + reverse("counter:details", kwargs={"counter_id": self.mde.id}) + ) self.assertTrue( - 'class="link-button">S' Kia' in str(response.content)) + 'class="link-button">S' Kia' in str(response.content) + ) counter_token = re.search( - r'name="counter_token" value="([^"]*)"', str(response.content)).group(1) + r'name="counter_token" value="([^"]*)"', str(response.content) + ).group(1) - response = self.client.post(reverse("counter:details", - kwargs={"counter_id": self.mde.id}), { - "code": "4000k", - "counter_token": counter_token, - }) - location = response.get('location') + response = self.client.post( + reverse("counter:details", kwargs={"counter_id": self.mde.id}), + {"code": "4000k", "counter_token": counter_token}, + ) + location = response.get("location") - response = self.client.get(response.get('location')) - self.assertTrue('>Richard BatsbakRichard BatsbakClient : Richard Batsbak - Nouveau montant : 8.30' in str(response_get.content)) + response_get = self.client.get(response.get("location")) + self.assertTrue( + "

    Client : Richard Batsbak - Nouveau montant : 8.30" + in str(response_get.content) + ) class BarmanConnectionTest(TestCase): @@ -93,40 +95,50 @@ class BarmanConnectionTest(TestCase): self.counter = Counter.objects.filter(id=2).first() def test_barman_granted(self): - self.client.post(reverse('counter:login', args=[self.counter.id]), - {'username': "krophil", - 'password': "plop"}) - response_get = self.client.get(reverse("counter:details", - args=[self.counter.id])) + self.client.post( + reverse("counter:login", args=[self.counter.id]), + {"username": "krophil", "password": "plop"}, + ) + response_get = self.client.get( + reverse("counter:details", args=[self.counter.id]) + ) - self.assertTrue( - '

    Entrez un code client :

    ' in str(response_get.content)) + self.assertTrue("

    Entrez un code client :

    " in str(response_get.content)) def test_counters_list_barmen(self): - self.client.post(reverse('counter:login', args=[self.counter.id]), - {'username': "krophil", - 'password': "plop"}) - response_get = self.client.get(reverse("counter:activity", - args=[self.counter.id])) + self.client.post( + reverse("counter:login", args=[self.counter.id]), + {"username": "krophil", "password": "plop"}, + ) + response_get = self.client.get( + reverse("counter:activity", args=[self.counter.id]) + ) self.assertTrue( - '
  • Kro Phil'
  • ' in str(response_get.content)) + '
  • Kro Phil'
  • ' + in str(response_get.content) + ) def test_barman_denied(self): - self.client.post(reverse('counter:login', args=[self.counter.id]), - {'username': "skia", - 'password': "plop"}) + self.client.post( + reverse("counter:login", args=[self.counter.id]), + {"username": "skia", "password": "plop"}, + ) response_get = self.client.get( - reverse("counter:details", args=[self.counter.id])) + reverse("counter:details", args=[self.counter.id]) + ) - self.assertTrue('

    Merci de vous identifier

    ' in str(response_get.content)) + self.assertTrue("

    Merci de vous identifier

    " in str(response_get.content)) def test_counters_list_no_barmen(self): - self.client.post(reverse('counter:login', args=[self.counter.id]), - {'username': "krophil", - 'password': "plop"}) + self.client.post( + reverse("counter:login", args=[self.counter.id]), + {"username": "krophil", "password": "plop"}, + ) response_get = self.client.get( - reverse("counter:activity", args=[self.counter.id])) + reverse("counter:activity", args=[self.counter.id]) + ) self.assertFalse( - '
  • S' Kia
  • ' in str(response_get.content)) + '
  • S' Kia
  • ' in str(response_get.content) + ) diff --git a/counter/urls.py b/counter/urls.py index aaade292..9b99b604 100644 --- a/counter/urls.py +++ b/counter/urls.py @@ -27,37 +27,106 @@ from django.conf.urls import url from counter.views import * urlpatterns = [ - url(r'^(?P[0-9]+)$', CounterMain.as_view(), name='details'), - url(r'^(?P[0-9]+)/click/(?P[0-9]+)$', CounterClick.as_view(), name='click'), - url(r'^(?P[0-9]+)/last_ops$', CounterLastOperationsView.as_view(), name='last_ops'), - url(r'^(?P[0-9]+)/cash_summary$', CounterCashSummaryView.as_view(), name='cash_summary'), - url(r'^(?P[0-9]+)/activity$', CounterActivityView.as_view(), name='activity'), - url(r'^(?P[0-9]+)/stats$', CounterStatView.as_view(), name='stats'), - url(r'^(?P[0-9]+)/login$', CounterLogin.as_view(), name='login'), - url(r'^(?P[0-9]+)/logout$', CounterLogout.as_view(), name='logout'), - url(r'^eticket/(?P[0-9]+)/pdf$', EticketPDFView.as_view(), name='eticket_pdf'), - url(r'^admin/(?P[0-9]+)$', CounterEditView.as_view(), name='admin'), - url(r'^admin/(?P[0-9]+)/prop$', CounterEditPropView.as_view(), name='prop_admin'), - url(r'^admin$', CounterListView.as_view(), name='admin_list'), - url(r'^admin/new$', CounterCreateView.as_view(), name='new'), - url(r'^admin/delete/(?P[0-9]+)$', CounterDeleteView.as_view(), name='delete'), - url(r'^admin/invoices_call$', InvoiceCallView.as_view(), name='invoices_call'), - url(r'^admin/cash_summary/list$', CashSummaryListView.as_view(), name='cash_summary_list'), - url(r'^admin/cash_summary/(?P[0-9]+)$', CashSummaryEditView.as_view(), name='cash_summary_edit'), - url(r'^admin/product/list$', ProductListView.as_view(), name='product_list'), - url(r'^admin/product/list_archived$', ProductArchivedListView.as_view(), name='product_list_archived'), - url(r'^admin/product/create$', ProductCreateView.as_view(), name='new_product'), - url(r'^admin/product/(?P[0-9]+)$', ProductEditView.as_view(), name='product_edit'), - url(r'^admin/producttype/list$', ProductTypeListView.as_view(), name='producttype_list'), - url(r'^admin/producttype/create$', ProductTypeCreateView.as_view(), name='new_producttype'), - url(r'^admin/producttype/(?P[0-9]+)$', ProductTypeEditView.as_view(), name='producttype_edit'), - url(r'^admin/eticket/list$', EticketListView.as_view(), name='eticket_list'), - url(r'^admin/eticket/new$', EticketCreateView.as_view(), name='new_eticket'), - url(r'^admin/eticket/(?P[0-9]+)$', EticketEditView.as_view(), name='edit_eticket'), - url(r'^admin/selling/(?P[0-9]+)/delete$', SellingDeleteView.as_view(), name='selling_delete'), - url(r'^admin/refilling/(?P[0-9]+)/delete$', RefillingDeleteView.as_view(), name='refilling_delete'), - url(r'^admin/(?P[0-9]+)/refillings$', CounterRefillingListView.as_view(), name='refilling_list'), - + url(r"^(?P[0-9]+)$", CounterMain.as_view(), name="details"), + url( + r"^(?P[0-9]+)/click/(?P[0-9]+)$", + CounterClick.as_view(), + name="click", + ), + url( + r"^(?P[0-9]+)/last_ops$", + CounterLastOperationsView.as_view(), + name="last_ops", + ), + url( + r"^(?P[0-9]+)/cash_summary$", + CounterCashSummaryView.as_view(), + name="cash_summary", + ), + url( + r"^(?P[0-9]+)/activity$", + CounterActivityView.as_view(), + name="activity", + ), + url(r"^(?P[0-9]+)/stats$", CounterStatView.as_view(), name="stats"), + url(r"^(?P[0-9]+)/login$", CounterLogin.as_view(), name="login"), + url(r"^(?P[0-9]+)/logout$", CounterLogout.as_view(), name="logout"), + url( + r"^eticket/(?P[0-9]+)/pdf$", + EticketPDFView.as_view(), + name="eticket_pdf", + ), + url(r"^admin/(?P[0-9]+)$", CounterEditView.as_view(), name="admin"), + url( + r"^admin/(?P[0-9]+)/prop$", + CounterEditPropView.as_view(), + name="prop_admin", + ), + url(r"^admin$", CounterListView.as_view(), name="admin_list"), + url(r"^admin/new$", CounterCreateView.as_view(), name="new"), + url( + r"^admin/delete/(?P[0-9]+)$", + CounterDeleteView.as_view(), + name="delete", + ), + url(r"^admin/invoices_call$", InvoiceCallView.as_view(), name="invoices_call"), + url( + r"^admin/cash_summary/list$", + CashSummaryListView.as_view(), + name="cash_summary_list", + ), + url( + r"^admin/cash_summary/(?P[0-9]+)$", + CashSummaryEditView.as_view(), + name="cash_summary_edit", + ), + url(r"^admin/product/list$", ProductListView.as_view(), name="product_list"), + url( + r"^admin/product/list_archived$", + ProductArchivedListView.as_view(), + name="product_list_archived", + ), + url(r"^admin/product/create$", ProductCreateView.as_view(), name="new_product"), + url( + r"^admin/product/(?P[0-9]+)$", + ProductEditView.as_view(), + name="product_edit", + ), + url( + r"^admin/producttype/list$", + ProductTypeListView.as_view(), + name="producttype_list", + ), + url( + r"^admin/producttype/create$", + ProductTypeCreateView.as_view(), + name="new_producttype", + ), + url( + r"^admin/producttype/(?P[0-9]+)$", + ProductTypeEditView.as_view(), + name="producttype_edit", + ), + url(r"^admin/eticket/list$", EticketListView.as_view(), name="eticket_list"), + url(r"^admin/eticket/new$", EticketCreateView.as_view(), name="new_eticket"), + url( + r"^admin/eticket/(?P[0-9]+)$", + EticketEditView.as_view(), + name="edit_eticket", + ), + url( + r"^admin/selling/(?P[0-9]+)/delete$", + SellingDeleteView.as_view(), + name="selling_delete", + ), + url( + r"^admin/refilling/(?P[0-9]+)/delete$", + RefillingDeleteView.as_view(), + name="refilling_delete", + ), + url( + r"^admin/(?P[0-9]+)/refillings$", + CounterRefillingListView.as_view(), + name="refilling_list", + ), ] - - diff --git a/counter/views.py b/counter/views.py index 6a39619e..941cbc80 100644 --- a/counter/views.py +++ b/counter/views.py @@ -27,7 +27,13 @@ from django.http import Http404 from django.core.exceptions import PermissionDenied from django.views.generic import ListView, DetailView, RedirectView, TemplateView from django.views.generic.base import View -from django.views.generic.edit import UpdateView, CreateView, DeleteView, ProcessFormView, FormMixin +from django.views.generic.edit import ( + UpdateView, + CreateView, + DeleteView, + ProcessFormView, + FormMixin, +) from django.forms.models import modelform_factory from django.forms import CheckboxSelectMultiple from django.core.urlresolvers import reverse_lazy, reverse @@ -48,8 +54,18 @@ from core.views import CanViewMixin, TabedViewMixin from core.views.forms import LoginForm, SelectDate, SelectDateTime from core.models import User from subscription.models import Subscription -from counter.models import Counter, Customer, Product, Selling, Refilling, ProductType, \ - CashRegisterSummary, CashRegisterSummaryItem, Eticket, Permanency +from counter.models import ( + Counter, + Customer, + Product, + Selling, + Refilling, + ProductType, + CashRegisterSummary, + CashRegisterSummaryItem, + Eticket, + Permanency, +) from accounting.models import CurrencyField @@ -57,6 +73,7 @@ class CounterAdminMixin(View): """ This view is made to protect counter admin section """ + edit_group = [settings.SITH_GROUP_COUNTER_ADMIN_ID] edit_club = [] @@ -73,8 +90,11 @@ class CounterAdminMixin(View): return False def dispatch(self, request, *args, **kwargs): - if not (request.user.is_root or self._test_group(request.user) - or self._test_club(request.user)): + if not ( + request.user.is_root + or self._test_group(request.user) + or self._test_club(request.user) + ): raise PermissionDenied return super(CounterAdminMixin, self).dispatch(request, *args, **kwargs) @@ -87,134 +107,196 @@ class GetUserForm(forms.Form): The Form implements a nice JS widget allowing the user to type a customer account id, or search the database with some nickname, first name, or last name (TODO) """ + code = forms.CharField(label="Code", max_length=10, required=False) - id = AutoCompleteSelectField('users', required=False, label=_("Select user"), help_text=None) + id = AutoCompleteSelectField( + "users", required=False, label=_("Select user"), help_text=None + ) def as_p(self): - self.fields['code'].widget.attrs['autofocus'] = True + self.fields["code"].widget.attrs["autofocus"] = True return super(GetUserForm, self).as_p() def clean(self): cleaned_data = super(GetUserForm, self).clean() cus = None - if cleaned_data['code'] != "": - cus = Customer.objects.filter(account_id__iexact=cleaned_data['code']).first() - elif cleaned_data['id'] is not None: - cus = Customer.objects.filter(user=cleaned_data['id']).first() - if (cus is None or not cus.can_buy): + if cleaned_data["code"] != "": + cus = Customer.objects.filter( + account_id__iexact=cleaned_data["code"] + ).first() + elif cleaned_data["id"] is not None: + cus = Customer.objects.filter(user=cleaned_data["id"]).first() + if cus is None or not cus.can_buy: raise forms.ValidationError(_("User not found")) - cleaned_data['user_id'] = cus.user.id - cleaned_data['user'] = cus.user + cleaned_data["user_id"] = cus.user.id + cleaned_data["user"] = cus.user return cleaned_data class RefillForm(forms.ModelForm): - error_css_class = 'error' - required_css_class = 'required' - amount = forms.FloatField(min_value=0, widget=forms.NumberInput(attrs={'class': 'focus'})) + error_css_class = "error" + required_css_class = "required" + amount = forms.FloatField( + min_value=0, widget=forms.NumberInput(attrs={"class": "focus"}) + ) class Meta: model = Refilling - fields = ['amount', 'payment_method', 'bank'] + fields = ["amount", "payment_method", "bank"] class CounterTabsMixin(TabedViewMixin): def get_tabs_title(self): - if hasattr(self.object, 'stock_owner'): + if hasattr(self.object, "stock_owner"): return self.object.stock_owner.counter else: return self.object def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse_lazy('counter:details', - kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}), - 'slug': 'counter', - 'name': _("Counter"), - }) - if self.object.stock_owner.counter.type if hasattr(self.object, 'stock_owner') else self.object.type == "BAR": - tab_list.append({ - 'url': reverse_lazy('counter:cash_summary', - kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}), - 'slug': 'cash_summary', - 'name': _("Cash summary"), - }) - tab_list.append({ - 'url': reverse_lazy('counter:last_ops', - kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}), - 'slug': 'last_ops', - 'name': _("Last operations"), - }) + tab_list.append( + { + "url": reverse_lazy( + "counter:details", + kwargs={ + "counter_id": self.object.stock_owner.counter.id + if hasattr(self.object, "stock_owner") + else self.object.id + }, + ), + "slug": "counter", + "name": _("Counter"), + } + ) + if ( + self.object.stock_owner.counter.type + if hasattr(self.object, "stock_owner") + else self.object.type == "BAR" + ): + tab_list.append( + { + "url": reverse_lazy( + "counter:cash_summary", + kwargs={ + "counter_id": self.object.stock_owner.counter.id + if hasattr(self.object, "stock_owner") + else self.object.id + }, + ), + "slug": "cash_summary", + "name": _("Cash summary"), + } + ) + tab_list.append( + { + "url": reverse_lazy( + "counter:last_ops", + kwargs={ + "counter_id": self.object.stock_owner.counter.id + if hasattr(self.object, "stock_owner") + else self.object.id + }, + ), + "slug": "last_ops", + "name": _("Last operations"), + } + ) try: - tab_list.append({ - 'url': reverse_lazy('stock:take_items', - kwargs={'stock_id': self.object.stock.id if hasattr(self.object, 'stock') else self.object.stock_owner.id}), - 'slug': 'take_items_from_stock', - 'name': _("Take items from stock"), - }) + tab_list.append( + { + "url": reverse_lazy( + "stock:take_items", + kwargs={ + "stock_id": self.object.stock.id + if hasattr(self.object, "stock") + else self.object.stock_owner.id + }, + ), + "slug": "take_items_from_stock", + "name": _("Take items from stock"), + } + ) except: pass # The counter just have no stock return tab_list -class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin): +class CounterMain( + CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin +): """ The public (barman) view """ + model = Counter - template_name = 'counter/counter_main.jinja' + template_name = "counter/counter_main.jinja" pk_url_kwarg = "counter_id" - form_class = GetUserForm # Form to enter a client code and get the corresponding user id + form_class = ( + GetUserForm + ) # Form to enter a client code and get the corresponding user id current_tab = "counter" def post(self, request, *args, **kwargs): self.object = self.get_object() - if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and - self.request.session['counter_token'] == self.object.token): # Check the token to avoid the bar to be stolen - return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, - kwargs={'counter_id': self.object.id}) + '?bad_location') + if self.object.type == "BAR" and not ( + "counter_token" in self.request.session.keys() + and self.request.session["counter_token"] == self.object.token + ): # Check the token to avoid the bar to be stolen + return HttpResponseRedirect( + reverse_lazy( + "counter:details", + args=self.args, + kwargs={"counter_id": self.object.id}, + ) + + "?bad_location" + ) return super(CounterMain, self).post(request, *args, **kwargs) def get_context_data(self, **kwargs): """ We handle here the login form for the barman """ - if self.request.method == 'POST': + if self.request.method == "POST": self.object = self.get_object() self.object.update_activity() kwargs = super(CounterMain, self).get_context_data(**kwargs) - kwargs['login_form'] = LoginForm() - kwargs['login_form'].fields['username'].widget.attrs['autofocus'] = True - kwargs['login_form'].cleaned_data = {} # add_error fails if there are no cleaned_data + kwargs["login_form"] = LoginForm() + kwargs["login_form"].fields["username"].widget.attrs["autofocus"] = True + kwargs[ + "login_form" + ].cleaned_data = {} # add_error fails if there are no cleaned_data if "credentials" in self.request.GET: - kwargs['login_form'].add_error(None, _("Bad credentials")) + kwargs["login_form"].add_error(None, _("Bad credentials")) if "sellers" in self.request.GET: - kwargs['login_form'].add_error(None, _("User is not barman")) - kwargs['form'] = self.get_form() - kwargs['form'].cleaned_data = {} # same as above + kwargs["login_form"].add_error(None, _("User is not barman")) + kwargs["form"] = self.get_form() + kwargs["form"].cleaned_data = {} # same as above if "bad_location" in self.request.GET: - kwargs['form'].add_error(None, _("Bad location, someone is already logged in somewhere else")) - if self.object.type == 'BAR': - kwargs['barmen'] = self.object.get_barmen_list() + kwargs["form"].add_error( + None, _("Bad location, someone is already logged in somewhere else") + ) + if self.object.type == "BAR": + kwargs["barmen"] = self.object.get_barmen_list() elif self.request.user.is_authenticated(): - kwargs['barmen'] = [self.request.user] - if 'last_basket' in self.request.session.keys(): - kwargs['last_basket'] = self.request.session.pop('last_basket') - kwargs['last_customer'] = self.request.session.pop('last_customer') - kwargs['last_total'] = self.request.session.pop('last_total') - kwargs['new_customer_amount'] = self.request.session.pop('new_customer_amount') + kwargs["barmen"] = [self.request.user] + if "last_basket" in self.request.session.keys(): + kwargs["last_basket"] = self.request.session.pop("last_basket") + kwargs["last_customer"] = self.request.session.pop("last_customer") + kwargs["last_total"] = self.request.session.pop("last_total") + kwargs["new_customer_amount"] = self.request.session.pop( + "new_customer_amount" + ) return kwargs def form_valid(self, form): """ We handle here the redirection, passing the user id of the asked customer """ - self.kwargs['user_id'] = form.cleaned_data['user_id'] + self.kwargs["user_id"] = form.cleaned_data["user_id"] return super(CounterMain, self).form_valid(form) def get_success_url(self): - return reverse_lazy('counter:click', args=self.args, kwargs=self.kwargs) + return reverse_lazy("counter:click", args=self.args, kwargs=self.kwargs) class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): @@ -223,19 +305,25 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): This is a detail view not to have to worry about loading the counter Everything is made by hand in the post method """ + model = Counter - template_name = 'counter/counter_click.jinja' + template_name = "counter/counter_click.jinja" pk_url_kwarg = "counter_id" current_tab = "counter" def dispatch(self, request, *args, **kwargs): - self.customer = get_object_or_404(Customer, user__id=self.kwargs['user_id']) + self.customer = get_object_or_404(Customer, user__id=self.kwargs["user_id"]) obj = self.get_object() if not self.customer.can_buy: raise Http404 if obj.type == "BAR": - if not ('counter_token' in request.session.keys() and - request.session['counter_token'] == obj.token) or len(obj.get_barmen_list()) < 1: + if ( + not ( + "counter_token" in request.session.keys() + and request.session["counter_token"] == obj.token + ) + or len(obj.get_barmen_list()) < 1 + ): raise PermissionDenied else: if not request.user.is_authenticated(): @@ -244,18 +332,18 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def get(self, request, *args, **kwargs): """Simple get view""" - if 'basket' not in request.session.keys(): # Init the basket session entry - request.session['basket'] = {} - request.session['basket_total'] = 0 - request.session['not_enough'] = False # Reset every variable - request.session['too_young'] = False - request.session['not_allowed'] = False - request.session['no_age'] = False + if "basket" not in request.session.keys(): # Init the basket session entry + request.session["basket"] = {} + request.session["basket_total"] = 0 + request.session["not_enough"] = False # Reset every variable + request.session["too_young"] = False + request.session["not_allowed"] = False + request.session["no_age"] = False self.refill_form = None ret = super(CounterClick, self).get(request, *args, **kwargs) - if ((self.object.type != "BAR" and not request.user.is_authenticated()) or - (self.object.type == "BAR" and - len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in + if (self.object.type != "BAR" and not request.user.is_authenticated()) or ( + self.object.type == "BAR" and len(self.object.get_barmen_list()) < 1 + ): # Check that at least one barman is logged in ret = self.cancel(request) # Otherwise, go to main view return ret @@ -263,21 +351,29 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): """ Handle the many possibilities of the post request """ self.object = self.get_object() self.refill_form = None - if ((self.object.type != "BAR" and not request.user.is_authenticated()) or - (self.object.type == "BAR" and - len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in + if (self.object.type != "BAR" and not request.user.is_authenticated()) or ( + self.object.type == "BAR" and len(self.object.get_barmen_list()) < 1 + ): # Check that at least one barman is logged in return self.cancel(request) - if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and - self.request.session['counter_token'] == self.object.token): # Also check the token to avoid the bar to be stolen - return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, - kwargs={'counter_id': self.object.id}) + '?bad_location') - if 'basket' not in request.session.keys(): - request.session['basket'] = {} - request.session['basket_total'] = 0 - request.session['not_enough'] = False # Reset every variable - request.session['too_young'] = False - request.session['not_allowed'] = False - request.session['no_age'] = False + if self.object.type == "BAR" and not ( + "counter_token" in self.request.session.keys() + and self.request.session["counter_token"] == self.object.token + ): # Also check the token to avoid the bar to be stolen + return HttpResponseRedirect( + reverse_lazy( + "counter:details", + args=self.args, + kwargs={"counter_id": self.object.id}, + ) + + "?bad_location" + ) + if "basket" not in request.session.keys(): + request.session["basket"] = {} + request.session["basket_total"] = 0 + request.session["not_enough"] = False # Reset every variable + request.session["too_young"] = False + request.session["not_allowed"] = False + request.session["no_age"] = False if self.object.type != "BAR": self.operator = request.user elif self.is_barman_price(): @@ -285,23 +381,25 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): else: self.operator = self.object.get_random_barman() - if 'add_product' in request.POST['action']: + if "add_product" in request.POST["action"]: self.add_product(request) - elif 'del_product' in request.POST['action']: + elif "del_product" in request.POST["action"]: self.del_product(request) - elif 'refill' in request.POST['action']: + elif "refill" in request.POST["action"]: self.refill(request) - elif 'code' in request.POST['action']: + elif "code" in request.POST["action"]: return self.parse_code(request) - elif 'cancel' in request.POST['action']: + elif "cancel" in request.POST["action"]: return self.cancel(request) - elif 'finish' in request.POST['action']: + elif "finish" in request.POST["action"]: return self.finish(request) context = self.get_context_data(object=self.object) return self.render_to_response(context) def is_barman_price(self): - if self.object.type == "BAR" and self.customer.user.id in [s.id for s in self.object.get_barmen_list()]: + if self.object.type == "BAR" and self.customer.user.id in [ + s.id for s in self.object.get_barmen_list() + ]: return True else: return False @@ -319,20 +417,23 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def sum_basket(self, request): total = 0 - for pid, infos in request.session['basket'].items(): - total += infos['price'] * infos['qty'] + for pid, infos in request.session["basket"].items(): + total += infos["price"] * infos["qty"] return total / 100 def get_total_quantity_for_pid(self, request, pid): pid = str(pid) try: - return request.session['basket'][pid]['qty'] + request.session['basket'][pid]['bonus_qty'] + return ( + request.session["basket"][pid]["qty"] + + request.session["basket"][pid]["bonus_qty"] + ) except: return 0 def compute_record_product(self, request, product=None): recorded = 0 - basket = request.session['basket'] + basket = request.session["basket"] if product: if product.is_record_product: @@ -343,14 +444,15 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): for p in basket: bproduct = self.get_product(str(p)) if bproduct.is_record_product: - recorded -= basket[p]['qty'] + recorded -= basket[p]["qty"] elif bproduct.is_unrecord_product: - recorded += basket[p]['qty'] + recorded += basket[p]["qty"] return recorded def is_record_product_ok(self, request, product): return self.customer.can_record_more( - self.compute_record_product(request, product)) + self.compute_record_product(request, product) + ) def add_product(self, request, q=1, p=None): """ @@ -358,7 +460,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): q is the quantity passed as integer p is the product id, passed as an integer """ - pid = p or request.POST['product_id'] + pid = p or request.POST["product_id"] pid = str(pid) price = self.get_price(pid) total = self.sum_basket(request) @@ -371,57 +473,74 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): if self.customer.user.is_in_group(g.name): can_buy = True if not can_buy: - request.session['not_allowed'] = True + request.session["not_allowed"] = True return False bq = 0 # Bonus quantity, for trays - if product.tray: # Handle the tray to adjust the quantity q to add and the bonus quantity bq + if ( + product.tray + ): # Handle the tray to adjust the quantity q to add and the bonus quantity bq total_qty_mod_6 = self.get_total_quantity_for_pid(request, pid) % 6 bq = int((total_qty_mod_6 + q) / 6) # Integer division q -= bq - if self.customer.amount < (total + round(q * float(price), 2)): # Check for enough money - request.session['not_enough'] = True + if self.customer.amount < ( + total + round(q * float(price), 2) + ): # Check for enough money + request.session["not_enough"] = True return False - if product.is_unrecord_product and not self.is_record_product_ok(request, product): - request.session['not_allowed'] = True + if product.is_unrecord_product and not self.is_record_product_ok( + request, product + ): + request.session["not_allowed"] = True return False if product.limit_age >= 18 and not self.customer.user.date_of_birth: - request.session['no_age'] = True + request.session["no_age"] = True return False if product.limit_age >= 18 and self.customer.user.is_banned_alcohol: - request.session['not_allowed'] = True + request.session["not_allowed"] = True return False if self.customer.user.is_banned_counter: - request.session['not_allowed'] = True + request.session["not_allowed"] = True return False - if self.customer.user.date_of_birth and self.customer.user.get_age() < product.limit_age: # Check if affordable - request.session['too_young'] = True + if ( + self.customer.user.date_of_birth + and self.customer.user.get_age() < product.limit_age + ): # Check if affordable + request.session["too_young"] = True return False - if pid in request.session['basket']: # Add if already in basket - request.session['basket'][pid]['qty'] += q - request.session['basket'][pid]['bonus_qty'] += bq + if pid in request.session["basket"]: # Add if already in basket + request.session["basket"][pid]["qty"] += q + request.session["basket"][pid]["bonus_qty"] += bq else: # or create if not - request.session['basket'][pid] = {'qty': q, 'price': int(price * 100), 'bonus_qty': bq} + request.session["basket"][pid] = { + "qty": q, + "price": int(price * 100), + "bonus_qty": bq, + } request.session.modified = True return True def del_product(self, request): """ Delete a product from the basket """ - pid = str(request.POST['product_id']) + pid = str(request.POST["product_id"]) product = self.get_product(pid) - if pid in request.session['basket']: - if product.tray and (self.get_total_quantity_for_pid(request, pid) % 6 == 0) and request.session['basket'][pid]['bonus_qty']: - request.session['basket'][pid]['bonus_qty'] -= 1 + if pid in request.session["basket"]: + if ( + product.tray + and (self.get_total_quantity_for_pid(request, pid) % 6 == 0) + and request.session["basket"][pid]["bonus_qty"] + ): + request.session["basket"][pid]["bonus_qty"] -= 1 else: - request.session['basket'][pid]['qty'] -= 1 - if request.session['basket'][pid]['qty'] <= 0: - del request.session['basket'][pid] + request.session["basket"][pid]["qty"] -= 1 + if request.session["basket"][pid]["qty"] <= 0: + del request.session["basket"][pid] else: - request.session['basket'][pid] = None + request.session["basket"][pid] = None request.session.modified = True def parse_code(self, request): """Parse the string entered by the barman""" - string = str(request.POST['code']).upper() + string = str(request.POST["code"]).upper() if string == _("END"): return self.finish(request) elif string == _("CAN"): @@ -429,8 +548,8 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): regex = re.compile(r"^((?P[0-9]+)X)?(?P[A-Z0-9]+)$") m = regex.match(string) if m is not None: - nb = m.group('nb') - code = m.group('code') + nb = m.group("nb") + code = m.group("code") if nb is None: nb = 1 else: @@ -445,46 +564,66 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def finish(self, request): """ Finish the click session, and validate the basket """ with transaction.atomic(): - request.session['last_basket'] = [] + request.session["last_basket"] = [] if self.sum_basket(request) > self.customer.amount: raise DataError(_("You have not enough money to buy all the basket")) - for pid, infos in request.session['basket'].items(): + for pid, infos in request.session["basket"].items(): # This duplicates code for DB optimization (prevent to load many times the same object) p = Product.objects.filter(pk=pid).first() if self.is_barman_price(): uprice = p.special_selling_price else: uprice = p.selling_price - request.session['last_basket'].append("%d x %s" % (infos['qty'] + infos['bonus_qty'], p.name)) - s = Selling(label=p.name, product=p, club=p.club, counter=self.object, unit_price=uprice, - quantity=infos['qty'], seller=self.operator, customer=self.customer) + request.session["last_basket"].append( + "%d x %s" % (infos["qty"] + infos["bonus_qty"], p.name) + ) + s = Selling( + label=p.name, + product=p, + club=p.club, + counter=self.object, + unit_price=uprice, + quantity=infos["qty"], + seller=self.operator, + customer=self.customer, + ) s.save() - if infos['bonus_qty']: - s = Selling(label=p.name + " (Plateau)", product=p, club=p.club, counter=self.object, unit_price=0, - quantity=infos['bonus_qty'], seller=self.operator, customer=self.customer) + if infos["bonus_qty"]: + s = Selling( + label=p.name + " (Plateau)", + product=p, + club=p.club, + counter=self.object, + unit_price=0, + quantity=infos["bonus_qty"], + seller=self.operator, + customer=self.customer, + ) s.save() self.customer.recorded_products -= self.compute_record_product(request) self.customer.save() - request.session['last_customer'] = self.customer.user.get_display_name() - request.session['last_total'] = "%0.2f" % self.sum_basket(request) - request.session['new_customer_amount'] = str(self.customer.amount) - del request.session['basket'] + request.session["last_customer"] = self.customer.user.get_display_name() + request.session["last_total"] = "%0.2f" % self.sum_basket(request) + request.session["new_customer_amount"] = str(self.customer.amount) + del request.session["basket"] request.session.modified = True - kwargs = { - 'counter_id': self.object.id, - } - return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs)) + kwargs = {"counter_id": self.object.id} + return HttpResponseRedirect( + reverse_lazy("counter:details", args=self.args, kwargs=kwargs) + ) def cancel(self, request): """ Cancel the click session """ - kwargs = {'counter_id': self.object.id} - request.session.pop('basket', None) - return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs)) + kwargs = {"counter_id": self.object.id} + request.session.pop("basket", None) + return HttpResponseRedirect( + reverse_lazy("counter:details", args=self.args, kwargs=kwargs) + ) def refill(self, request): """Refill the customer's account""" - if self.get_object().type == 'BAR': + if self.get_object().type == "BAR": form = RefillForm(request.POST) if form.is_valid(): form.instance.counter = self.object @@ -499,10 +638,10 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): """ Add customer to the context """ kwargs = super(CounterClick, self).get_context_data(**kwargs) - kwargs['customer'] = self.customer - kwargs['basket_total'] = self.sum_basket(self.request) - kwargs['refill_form'] = self.refill_form or RefillForm() - kwargs['categories'] = ProductType.objects.all() + kwargs["customer"] = self.customer + kwargs["basket_total"] = self.sum_basket(self.request) + kwargs["refill_form"] = self.refill_form or RefillForm() + kwargs["categories"] = ProductType.objects.all() return kwargs @@ -512,22 +651,26 @@ class CounterLogin(RedirectView): Logged barmen are stored in the Permanency model """ + permanent = False def post(self, request, *args, **kwargs): """ Register the logged user as barman for this counter """ - self.counter_id = kwargs['counter_id'] - self.counter = Counter.objects.filter(id=kwargs['counter_id']).first() + self.counter_id = kwargs["counter_id"] + self.counter = Counter.objects.filter(id=kwargs["counter_id"]).first() form = LoginForm(request, data=request.POST) self.errors = [] if form.is_valid(): - user = User.objects.filter(username=form.cleaned_data['username']).first() - if user in self.counter.sellers.all() and not user in self.counter.get_barmen_list(): + user = User.objects.filter(username=form.cleaned_data["username"]).first() + if ( + user in self.counter.sellers.all() + and not user in self.counter.get_barmen_list() + ): if len(self.counter.get_barmen_list()) <= 0: self.counter.gen_token() - request.session['counter_token'] = self.counter.token + request.session["counter_token"] = self.counter.token self.counter.add_barman(user) else: self.errors += ["sellers"] @@ -536,7 +679,11 @@ class CounterLogin(RedirectView): return super(CounterLogin, self).post(request, *args, **kwargs) def get_redirect_url(self, *args, **kwargs): - return reverse_lazy('counter:details', args=args, kwargs=kwargs) + "?" + '&'.join(self.errors) + return ( + reverse_lazy("counter:details", args=args, kwargs=kwargs) + + "?" + + "&".join(self.errors) + ) class CounterLogout(RedirectView): @@ -546,13 +693,14 @@ class CounterLogout(RedirectView): """ Unregister the user from the barman """ - self.counter = Counter.objects.filter(id=kwargs['counter_id']).first() - user = User.objects.filter(id=request.POST['user_id']).first() + self.counter = Counter.objects.filter(id=kwargs["counter_id"]).first() + user = User.objects.filter(id=request.POST["user_id"]).first() self.counter.del_barman(user) return super(CounterLogout, self).post(request, *args, **kwargs) def get_redirect_url(self, *args, **kwargs): - return reverse_lazy('counter:details', args=args, kwargs=kwargs) + return reverse_lazy("counter:details", args=args, kwargs=kwargs) + # Counter admin views @@ -560,45 +708,41 @@ class CounterLogout(RedirectView): class CounterAdminTabsMixin(TabedViewMixin): tabs_title = _("Counter administration") list_of_tabs = [ + {"url": reverse_lazy("stock:list"), "slug": "stocks", "name": _("Stocks")}, { - 'url': reverse_lazy('stock:list'), - 'slug': 'stocks', - 'name': _("Stocks"), + "url": reverse_lazy("counter:admin_list"), + "slug": "counters", + "name": _("Counters"), }, { - 'url': reverse_lazy('counter:admin_list'), - 'slug': 'counters', - 'name': _("Counters"), + "url": reverse_lazy("counter:product_list"), + "slug": "products", + "name": _("Products"), }, { - 'url': reverse_lazy('counter:product_list'), - 'slug': 'products', - 'name': _("Products"), + "url": reverse_lazy("counter:product_list_archived"), + "slug": "archive", + "name": _("Archived products"), }, { - 'url': reverse_lazy('counter:product_list_archived'), - 'slug': 'archive', - 'name': _("Archived products"), + "url": reverse_lazy("counter:producttype_list"), + "slug": "product_types", + "name": _("Product types"), }, { - 'url': reverse_lazy('counter:producttype_list'), - 'slug': 'product_types', - 'name': _("Product types"), + "url": reverse_lazy("counter:cash_summary_list"), + "slug": "cash_summary", + "name": _("Cash register summaries"), }, { - 'url': reverse_lazy('counter:cash_summary_list'), - 'slug': 'cash_summary', - 'name': _("Cash register summaries"), + "url": reverse_lazy("counter:invoices_call"), + "slug": "invoices_call", + "name": _("Invoices call"), }, { - 'url': reverse_lazy('counter:invoices_call'), - 'slug': 'invoices_call', - 'name': _("Invoices call"), - }, - { - 'url': reverse_lazy('counter:eticket_list'), - 'slug': 'etickets', - 'name': _("Etickets"), + "url": reverse_lazy("counter:eticket_list"), + "slug": "etickets", + "name": _("Etickets"), }, ] @@ -607,27 +751,30 @@ class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView): """ A list view for the admins """ + model = Counter - template_name = 'counter/counter_list.jinja' + template_name = "counter/counter_list.jinja" current_tab = "counters" class CounterEditForm(forms.ModelForm): class Meta: model = Counter - fields = ['sellers', 'products'] - sellers = make_ajax_field(Counter, 'sellers', 'users', help_text="") - products = make_ajax_field(Counter, 'products', 'products', help_text="") + fields = ["sellers", "products"] + + sellers = make_ajax_field(Counter, "sellers", "users", help_text="") + products = make_ajax_field(Counter, "products", "products", help_text="") class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ Edit a counter's main informations (for the counter's manager) """ + model = Counter form_class = CounterEditForm pk_url_kwarg = "counter_id" - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "counters" def dispatch(self, request, *args, **kwargs): @@ -636,17 +783,18 @@ class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): return super(CounterEditView, self).dispatch(request, *args, **kwargs) def get_success_url(self): - return reverse_lazy('counter:admin', kwargs={'counter_id': self.object.id}) + return reverse_lazy("counter:admin", kwargs={"counter_id": self.object.id}) class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ Edit a counter's main informations (for the counter's admin) """ + model = Counter - form_class = modelform_factory(Counter, fields=['name', 'club', 'type']) + form_class = modelform_factory(Counter, fields=["name", "club", "type"]) pk_url_kwarg = "counter_id" - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "counters" @@ -654,10 +802,14 @@ class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """ Create a counter (for the admins) """ + model = Counter - form_class = modelform_factory(Counter, fields=['name', 'club', 'type', 'products'], - widgets={'products': CheckboxSelectMultiple}) - template_name = 'core/create.jinja' + form_class = modelform_factory( + Counter, + fields=["name", "club", "type", "products"], + widgets={"products": CheckboxSelectMultiple}, + ) + template_name = "core/create.jinja" current_tab = "counters" @@ -665,12 +817,14 @@ class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView): """ Delete a counter (for the admins) """ + model = Counter pk_url_kwarg = "counter_id" - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('counter:admin_list') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("counter:admin_list") current_tab = "counters" + # Product management @@ -678,8 +832,9 @@ class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """ A list view for the admins """ + model = ProductType - template_name = 'counter/producttype_list.jinja' + template_name = "counter/producttype_list.jinja" current_tab = "product_types" @@ -687,9 +842,10 @@ class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView """ A create view for the admins """ + model = ProductType - fields = ['name', 'description', 'comment', 'icon'] - template_name = 'core/create.jinja' + fields = ["name", "description", "comment", "icon"] + template_name = "core/create.jinja" current_tab = "products" @@ -697,9 +853,10 @@ class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ An edit view for the admins """ + model = ProductType - template_name = 'core/edit.jinja' - fields = ['name', 'description', 'comment', 'icon'] + template_name = "core/edit.jinja" + fields = ["name", "description", "comment", "icon"] pk_url_kwarg = "type_id" current_tab = "products" @@ -708,10 +865,11 @@ class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView """ A list view for the admins """ + model = Product - template_name = 'counter/product_list.jinja' + template_name = "counter/product_list.jinja" queryset = Product.objects.filter(archived=True) - ordering = ['name'] + ordering = ["name"] current_tab = "archive" @@ -719,36 +877,68 @@ class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """ A list view for the admins """ + model = Product - template_name = 'counter/product_list.jinja' + template_name = "counter/product_list.jinja" queryset = Product.objects.filter(archived=False) - ordering = ['name'] + ordering = ["name"] current_tab = "products" class ProductEditForm(forms.ModelForm): class Meta: model = Product - fields = ['name', 'description', 'product_type', 'code', 'parent_product', 'buying_groups', 'purchase_price', - 'selling_price', 'special_selling_price', 'icon', 'club', 'limit_age', 'tray', 'archived'] - parent_product = AutoCompleteSelectField('products', show_help_text=False, label=_("Parent product"), required=False) - buying_groups = AutoCompleteSelectMultipleField('groups', show_help_text=False, help_text="", label=_("Buying groups"), required=False) - club = AutoCompleteSelectField('clubs', show_help_text=False) - counters = AutoCompleteSelectMultipleField('counters', show_help_text=False, help_text="", label=_("Counters"), required=False) + fields = [ + "name", + "description", + "product_type", + "code", + "parent_product", + "buying_groups", + "purchase_price", + "selling_price", + "special_selling_price", + "icon", + "club", + "limit_age", + "tray", + "archived", + ] + + parent_product = AutoCompleteSelectField( + "products", show_help_text=False, label=_("Parent product"), required=False + ) + buying_groups = AutoCompleteSelectMultipleField( + "groups", + show_help_text=False, + help_text="", + label=_("Buying groups"), + required=False, + ) + club = AutoCompleteSelectField("clubs", show_help_text=False) + counters = AutoCompleteSelectMultipleField( + "counters", + show_help_text=False, + help_text="", + label=_("Counters"), + required=False, + ) def __init__(self, *args, **kwargs): super(ProductEditForm, self).__init__(*args, **kwargs) if self.instance.id: - self.fields['counters'].initial = [str(c.id) for c in self.instance.counters.all()] + self.fields["counters"].initial = [ + str(c.id) for c in self.instance.counters.all() + ] def save(self, *args, **kwargs): ret = super(ProductEditForm, self).save(*args, **kwargs) - if self.fields['counters'].initial: - for cid in self.fields['counters'].initial: + if self.fields["counters"].initial: + for cid in self.fields["counters"].initial: c = Counter.objects.filter(id=int(cid)).first() c.products.remove(self.instance) c.save() - for cid in self.cleaned_data['counters']: + for cid in self.cleaned_data["counters"]: c = Counter.objects.filter(id=int(cid)).first() c.products.add(self.instance) c.save() @@ -759,9 +949,10 @@ class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """ A create view for the admins """ + model = Product form_class = ProductEditForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" current_tab = "products" @@ -769,10 +960,11 @@ class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ An edit view for the admins """ + model = Product form_class = ProductEditForm pk_url_kwarg = "product_id" - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "products" @@ -780,23 +972,33 @@ class RefillingDeleteView(DeleteView): """ Delete a refilling (for the admins) """ + model = Refilling pk_url_kwarg = "refilling_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def dispatch(self, request, *args, **kwargs): """ We have here a very particular right handling, we can't inherit from CanEditPropMixin """ self.object = self.get_object() - if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) and - 'counter_token' in request.session.keys() and - request.session['counter_token'] and # check if not null for counters that have no token set - Counter.objects.filter(token=request.session['counter_token']).exists()): - self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id}) + if ( + timezone.now() - self.object.date + <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) + and "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter( # check if not null for counters that have no token set + token=request.session["counter_token"] + ).exists() + ): + self.success_url = reverse( + "counter:details", kwargs={"counter_id": self.object.counter.id} + ) return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs) elif self.object.is_owned_by(request.user): - self.success_url = reverse('core:user_account', kwargs={'user_id': self.object.customer.user.id}) + self.success_url = reverse( + "core:user_account", kwargs={"user_id": self.object.customer.user.id} + ) return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs) raise PermissionDenied @@ -805,26 +1007,37 @@ class SellingDeleteView(DeleteView): """ Delete a selling (for the admins) """ + model = Selling pk_url_kwarg = "selling_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def dispatch(self, request, *args, **kwargs): """ We have here a very particular right handling, we can't inherit from CanEditPropMixin """ self.object = self.get_object() - if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) and - 'counter_token' in request.session.keys() and - request.session['counter_token'] and # check if not null for counters that have no token set - Counter.objects.filter(token=request.session['counter_token']).exists()): - self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id}) + if ( + timezone.now() - self.object.date + <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) + and "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter( # check if not null for counters that have no token set + token=request.session["counter_token"] + ).exists() + ): + self.success_url = reverse( + "counter:details", kwargs={"counter_id": self.object.counter.id} + ) return super(SellingDeleteView, self).dispatch(request, *args, **kwargs) elif self.object.is_owned_by(request.user): - self.success_url = reverse('core:user_account', kwargs={'user_id': self.object.customer.user.id}) + self.success_url = reverse( + "core:user_account", kwargs={"user_id": self.object.customer.user.id} + ) return super(SellingDeleteView, self).dispatch(request, *args, **kwargs) raise PermissionDenied + # Cash register summaries @@ -832,6 +1045,7 @@ class CashRegisterSummaryForm(forms.Form): """ Provide the cash summary form """ + ten_cents = forms.IntegerField(label=_("10 cents"), required=False, min_value=0) twenty_cents = forms.IntegerField(label=_("20 cents"), required=False, min_value=0) fifty_cents = forms.IntegerField(label=_("50 cents"), required=False, min_value=0) @@ -841,46 +1055,108 @@ class CashRegisterSummaryForm(forms.Form): ten_euros = forms.IntegerField(label=_("10 euros"), required=False, min_value=0) twenty_euros = forms.IntegerField(label=_("20 euros"), required=False, min_value=0) fifty_euros = forms.IntegerField(label=_("50 euros"), required=False, min_value=0) - hundred_euros = forms.IntegerField(label=_("100 euros"), required=False, min_value=0) - check_1_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0) - check_1_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0) - check_2_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0) - check_2_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0) - check_3_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0) - check_3_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0) - check_4_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0) - check_4_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0) - check_5_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0) - check_5_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0) + hundred_euros = forms.IntegerField( + label=_("100 euros"), required=False, min_value=0 + ) + check_1_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_1_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_2_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_2_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_3_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_3_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_4_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_4_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_5_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_5_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) comment = forms.CharField(label=_("Comment"), required=False) emptied = forms.BooleanField(label=_("Emptied"), required=False) def __init__(self, *args, **kwargs): - instance = kwargs.pop('instance', None) + instance = kwargs.pop("instance", None) super(CashRegisterSummaryForm, self).__init__(*args, **kwargs) if instance: - self.fields['ten_cents'].initial = instance.ten_cents.quantity if instance.ten_cents else 0 - self.fields['twenty_cents'].initial = instance.twenty_cents.quantity if instance.twenty_cents else 0 - self.fields['fifty_cents'].initial = instance.fifty_cents.quantity if instance.fifty_cents else 0 - self.fields['one_euro'].initial = instance.one_euro.quantity if instance.one_euro else 0 - self.fields['two_euros'].initial = instance.two_euros.quantity if instance.two_euros else 0 - self.fields['five_euros'].initial = instance.five_euros.quantity if instance.five_euros else 0 - self.fields['ten_euros'].initial = instance.ten_euros.quantity if instance.ten_euros else 0 - self.fields['twenty_euros'].initial = instance.twenty_euros.quantity if instance.twenty_euros else 0 - self.fields['fifty_euros'].initial = instance.fifty_euros.quantity if instance.fifty_euros else 0 - self.fields['hundred_euros'].initial = instance.hundred_euros.quantity if instance.hundred_euros else 0 - self.fields['check_1_quantity'].initial = instance.check_1.quantity if instance.check_1 else 0 - self.fields['check_2_quantity'].initial = instance.check_2.quantity if instance.check_2 else 0 - self.fields['check_3_quantity'].initial = instance.check_3.quantity if instance.check_3 else 0 - self.fields['check_4_quantity'].initial = instance.check_4.quantity if instance.check_4 else 0 - self.fields['check_5_quantity'].initial = instance.check_5.quantity if instance.check_5 else 0 - self.fields['check_1_value'].initial = instance.check_1.value if instance.check_1 else 0 - self.fields['check_2_value'].initial = instance.check_2.value if instance.check_2 else 0 - self.fields['check_3_value'].initial = instance.check_3.value if instance.check_3 else 0 - self.fields['check_4_value'].initial = instance.check_4.value if instance.check_4 else 0 - self.fields['check_5_value'].initial = instance.check_5.value if instance.check_5 else 0 - self.fields['comment'].initial = instance.comment - self.fields['emptied'].initial = instance.emptied + self.fields["ten_cents"].initial = ( + instance.ten_cents.quantity if instance.ten_cents else 0 + ) + self.fields["twenty_cents"].initial = ( + instance.twenty_cents.quantity if instance.twenty_cents else 0 + ) + self.fields["fifty_cents"].initial = ( + instance.fifty_cents.quantity if instance.fifty_cents else 0 + ) + self.fields["one_euro"].initial = ( + instance.one_euro.quantity if instance.one_euro else 0 + ) + self.fields["two_euros"].initial = ( + instance.two_euros.quantity if instance.two_euros else 0 + ) + self.fields["five_euros"].initial = ( + instance.five_euros.quantity if instance.five_euros else 0 + ) + self.fields["ten_euros"].initial = ( + instance.ten_euros.quantity if instance.ten_euros else 0 + ) + self.fields["twenty_euros"].initial = ( + instance.twenty_euros.quantity if instance.twenty_euros else 0 + ) + self.fields["fifty_euros"].initial = ( + instance.fifty_euros.quantity if instance.fifty_euros else 0 + ) + self.fields["hundred_euros"].initial = ( + instance.hundred_euros.quantity if instance.hundred_euros else 0 + ) + self.fields["check_1_quantity"].initial = ( + instance.check_1.quantity if instance.check_1 else 0 + ) + self.fields["check_2_quantity"].initial = ( + instance.check_2.quantity if instance.check_2 else 0 + ) + self.fields["check_3_quantity"].initial = ( + instance.check_3.quantity if instance.check_3 else 0 + ) + self.fields["check_4_quantity"].initial = ( + instance.check_4.quantity if instance.check_4 else 0 + ) + self.fields["check_5_quantity"].initial = ( + instance.check_5.quantity if instance.check_5 else 0 + ) + self.fields["check_1_value"].initial = ( + instance.check_1.value if instance.check_1 else 0 + ) + self.fields["check_2_value"].initial = ( + instance.check_2.value if instance.check_2 else 0 + ) + self.fields["check_3_value"].initial = ( + instance.check_3.value if instance.check_3 else 0 + ) + self.fields["check_4_value"].initial = ( + instance.check_4.value if instance.check_4 else 0 + ) + self.fields["check_5_value"].initial = ( + instance.check_5.value if instance.check_5 else 0 + ) + self.fields["comment"].initial = instance.comment + self.fields["emptied"].initial = instance.emptied self.instance = instance else: self.instance = None @@ -888,50 +1164,89 @@ class CashRegisterSummaryForm(forms.Form): def save(self, counter=None): cd = self.cleaned_data summary = self.instance or CashRegisterSummary( - counter=counter, - user=counter.get_random_barman(), + counter=counter, user=counter.get_random_barman() ) - summary.comment = cd['comment'] - summary.emptied = cd['emptied'] + summary.comment = cd["comment"] + summary.emptied = cd["emptied"] summary.save() summary.items.all().delete() # Cash - if cd['ten_cents']: - CashRegisterSummaryItem(cash_summary=summary, value=0.1, quantity=cd['ten_cents']).save() - if cd['twenty_cents']: - CashRegisterSummaryItem(cash_summary=summary, value=0.2, quantity=cd['twenty_cents']).save() - if cd['fifty_cents']: - CashRegisterSummaryItem(cash_summary=summary, value=0.5, quantity=cd['fifty_cents']).save() - if cd['one_euro']: - CashRegisterSummaryItem(cash_summary=summary, value=1, quantity=cd['one_euro']).save() - if cd['two_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=2, quantity=cd['two_euros']).save() - if cd['five_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=5, quantity=cd['five_euros']).save() - if cd['ten_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=10, quantity=cd['ten_euros']).save() - if cd['twenty_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=20, quantity=cd['twenty_euros']).save() - if cd['fifty_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=50, quantity=cd['fifty_euros']).save() - if cd['hundred_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=100, quantity=cd['hundred_euros']).save() + if cd["ten_cents"]: + CashRegisterSummaryItem( + cash_summary=summary, value=0.1, quantity=cd["ten_cents"] + ).save() + if cd["twenty_cents"]: + CashRegisterSummaryItem( + cash_summary=summary, value=0.2, quantity=cd["twenty_cents"] + ).save() + if cd["fifty_cents"]: + CashRegisterSummaryItem( + cash_summary=summary, value=0.5, quantity=cd["fifty_cents"] + ).save() + if cd["one_euro"]: + CashRegisterSummaryItem( + cash_summary=summary, value=1, quantity=cd["one_euro"] + ).save() + if cd["two_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=2, quantity=cd["two_euros"] + ).save() + if cd["five_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=5, quantity=cd["five_euros"] + ).save() + if cd["ten_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=10, quantity=cd["ten_euros"] + ).save() + if cd["twenty_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=20, quantity=cd["twenty_euros"] + ).save() + if cd["fifty_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=50, quantity=cd["fifty_euros"] + ).save() + if cd["hundred_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=100, quantity=cd["hundred_euros"] + ).save() # Checks - if cd['check_1_quantity']: - CashRegisterSummaryItem(cash_summary=summary, value=cd['check_1_value'], - quantity=cd['check_1_quantity'], check=True).save() - if cd['check_2_quantity']: - CashRegisterSummaryItem(cash_summary=summary, value=cd['check_2_value'], - quantity=cd['check_2_quantity'], check=True).save() - if cd['check_3_quantity']: - CashRegisterSummaryItem(cash_summary=summary, value=cd['check_3_value'], - quantity=cd['check_3_quantity'], check=True).save() - if cd['check_4_quantity']: - CashRegisterSummaryItem(cash_summary=summary, value=cd['check_4_value'], - quantity=cd['check_4_quantity'], check=True).save() - if cd['check_5_quantity']: - CashRegisterSummaryItem(cash_summary=summary, value=cd['check_5_value'], - quantity=cd['check_5_quantity'], check=True).save() + if cd["check_1_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_1_value"], + quantity=cd["check_1_quantity"], + check=True, + ).save() + if cd["check_2_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_2_value"], + quantity=cd["check_2_quantity"], + check=True, + ).save() + if cd["check_3_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_3_value"], + quantity=cd["check_3_quantity"], + check=True, + ).save() + if cd["check_4_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_4_value"], + quantity=cd["check_4_quantity"], + check=True, + ).save() + if cd["check_5_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_5_value"], + quantity=cd["check_5_quantity"], + check=True, + ).save() if summary.items.count() < 1: summary.delete() @@ -940,9 +1255,10 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): """ Provide the last operations to allow barmen to delete them """ + model = Counter pk_url_kwarg = "counter_id" - template_name = 'counter/last_ops.jinja' + template_name = "counter/last_ops.jinja" current_tab = "last_ops" def dispatch(self, request, *args, **kwargs): @@ -950,18 +1266,34 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): We have here again a very particular right handling """ self.object = self.get_object() - if (self.object.get_barmen_list() and 'counter_token' in request.session.keys() and - request.session['counter_token'] and # check if not null for counters that have no token set - Counter.objects.filter(token=request.session['counter_token']).exists()): - return super(CounterLastOperationsView, self).dispatch(request, *args, **kwargs) - return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id}) + '?bad_location') + if ( + self.object.get_barmen_list() + and "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter( # check if not null for counters that have no token set + token=request.session["counter_token"] + ).exists() + ): + return super(CounterLastOperationsView, self).dispatch( + request, *args, **kwargs + ) + return HttpResponseRedirect( + reverse("counter:details", kwargs={"counter_id": self.object.id}) + + "?bad_location" + ) def get_context_data(self, **kwargs): """Add form to the context """ kwargs = super(CounterLastOperationsView, self).get_context_data(**kwargs) - threshold = timezone.now() - timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) - kwargs['last_refillings'] = self.object.refillings.filter(date__gte=threshold).order_by('-id')[:20] - kwargs['last_sellings'] = self.object.sellings.filter(date__gte=threshold).order_by('-id')[:20] + threshold = timezone.now() - timedelta( + minutes=settings.SITH_LAST_OPERATIONS_LIMIT + ) + kwargs["last_refillings"] = self.object.refillings.filter( + date__gte=threshold + ).order_by("-id")[:20] + kwargs["last_sellings"] = self.object.sellings.filter( + date__gte=threshold + ).order_by("-id")[:20] return kwargs @@ -969,9 +1301,10 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): """ Provide the cash summary form """ + model = Counter pk_url_kwarg = "counter_id" - template_name = 'counter/cash_register_summary.jinja' + template_name = "counter/cash_register_summary.jinja" current_tab = "cash_summary" def dispatch(self, request, *args, **kwargs): @@ -979,11 +1312,21 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): We have here again a very particular right handling """ self.object = self.get_object() - if (self.object.get_barmen_list() and 'counter_token' in request.session.keys() and - request.session['counter_token'] and # check if not null for counters that have no token set - Counter.objects.filter(token=request.session['counter_token']).exists()): - return super(CounterCashSummaryView, self).dispatch(request, *args, **kwargs) - return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id}) + '?bad_location') + if ( + self.object.get_barmen_list() + and "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter( # check if not null for counters that have no token set + token=request.session["counter_token"] + ).exists() + ): + return super(CounterCashSummaryView, self).dispatch( + request, *args, **kwargs + ) + return HttpResponseRedirect( + reverse("counter:details", kwargs={"counter_id": self.object.id}) + + "?bad_location" + ) def get(self, request, *args, **kwargs): self.object = self.get_object() @@ -999,12 +1342,12 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): return super(CounterCashSummaryView, self).get(request, *args, **kwargs) def get_success_url(self): - return reverse_lazy('counter:details', kwargs={'counter_id': self.object.id}) + return reverse_lazy("counter:details", kwargs={"counter_id": self.object.id}) def get_context_data(self, **kwargs): """ Add form to the context """ kwargs = super(CounterCashSummaryView, self).get_context_data(**kwargs) - kwargs['form'] = self.form + kwargs["form"] = self.form return kwargs @@ -1012,159 +1355,262 @@ class CounterActivityView(DetailView): """ Show the bar activity """ + model = Counter pk_url_kwarg = "counter_id" - template_name = 'counter/activity.jinja' + template_name = "counter/activity.jinja" class CounterStatView(DetailView, CounterAdminMixin): """ Show the bar stats """ + model = Counter pk_url_kwarg = "counter_id" - template_name = 'counter/stats.jinja' + template_name = "counter/stats.jinja" def get_context_data(self, **kwargs): """ Add stats to the context """ from django.db.models import Sum, Case, When, F, DecimalField - kwargs = super(CounterStatView, self).get_context_data(**kwargs) - kwargs['Customer'] = Customer - kwargs['User'] = User - semester_start = Subscription.compute_start(d=date.today(), duration=3) - kwargs['total_sellings'] = Selling.objects.filter(date__gte=semester_start, - counter=self.object).aggregate(total_sellings=Sum(F('quantity') * F('unit_price'), - output_field=CurrencyField()))['total_sellings'] - kwargs['top'] = Selling.objects.values('customer__user').annotate( - selling_sum=Sum( - Case(When(counter=self.object, - date__gte=semester_start, - unit_price__gt=0, - then=F('unit_price') * F('quantity')), - output_field=CurrencyField() - ) - ) - ).exclude(selling_sum=None).order_by('-selling_sum').all()[:100] - kwargs['top_barman'] = Permanency.objects.values('user').annotate( - perm_sum=Sum( - Case(When(counter=self.object, - end__gt=datetime(year=1999, month=1, day=1), - then=F('end') - F('start')), - output_field=models.DateTimeField() - ) - ) - ).exclude(perm_sum=None).order_by('-perm_sum').all()[:100] - kwargs['top_barman_semester'] = Permanency.objects.values('user').annotate( - perm_sum=Sum( - Case(When(counter=self.object, - start__gt=semester_start, - end__gt=datetime(year=1999, month=1, day=1), - then=F('end') - F('start')), - output_field=models.DateTimeField() - ) - ) - ).exclude(perm_sum=None).order_by('-perm_sum').all()[:100] - kwargs['sith_date'] = settings.SITH_START_DATE[0] - kwargs['semester_start'] = semester_start + kwargs = super(CounterStatView, self).get_context_data(**kwargs) + kwargs["Customer"] = Customer + kwargs["User"] = User + semester_start = Subscription.compute_start(d=date.today(), duration=3) + kwargs["total_sellings"] = Selling.objects.filter( + date__gte=semester_start, counter=self.object + ).aggregate( + total_sellings=Sum( + F("quantity") * F("unit_price"), output_field=CurrencyField() + ) + )[ + "total_sellings" + ] + kwargs["top"] = ( + Selling.objects.values("customer__user") + .annotate( + selling_sum=Sum( + Case( + When( + counter=self.object, + date__gte=semester_start, + unit_price__gt=0, + then=F("unit_price") * F("quantity"), + ), + output_field=CurrencyField(), + ) + ) + ) + .exclude(selling_sum=None) + .order_by("-selling_sum") + .all()[:100] + ) + kwargs["top_barman"] = ( + Permanency.objects.values("user") + .annotate( + perm_sum=Sum( + Case( + When( + counter=self.object, + end__gt=datetime(year=1999, month=1, day=1), + then=F("end") - F("start"), + ), + output_field=models.DateTimeField(), + ) + ) + ) + .exclude(perm_sum=None) + .order_by("-perm_sum") + .all()[:100] + ) + kwargs["top_barman_semester"] = ( + Permanency.objects.values("user") + .annotate( + perm_sum=Sum( + Case( + When( + counter=self.object, + start__gt=semester_start, + end__gt=datetime(year=1999, month=1, day=1), + then=F("end") - F("start"), + ), + output_field=models.DateTimeField(), + ) + ) + ) + .exclude(perm_sum=None) + .order_by("-perm_sum") + .all()[:100] + ) + + kwargs["sith_date"] = settings.SITH_START_DATE[0] + kwargs["semester_start"] = semester_start return kwargs def dispatch(self, request, *args, **kwargs): try: return super(CounterStatView, self).dispatch(request, *args, **kwargs) except: - if (request.user.is_root + if ( + request.user.is_root or request.user.is_board_member - or self.object.is_owned_by(request.user)): + or self.object.is_owned_by(request.user) + ): return super(CanEditMixin, self).dispatch(request, *args, **kwargs) raise PermissionDenied class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """Edit cash summaries""" + model = CashRegisterSummary - template_name = 'counter/cash_register_summary.jinja' + template_name = "counter/cash_register_summary.jinja" context_object_name = "cashsummary" pk_url_kwarg = "cashsummary_id" form_class = CashRegisterSummaryForm current_tab = "cash_summary" def get_success_url(self): - return reverse('counter:cash_summary_list') + return reverse("counter:cash_summary_list") class CashSummaryFormBase(forms.Form): - begin_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Begin date"), required=False, widget=SelectDateTime) - end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), required=False, widget=SelectDateTime) + begin_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Begin date"), + required=False, + widget=SelectDateTime, + ) + end_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End date"), + required=False, + widget=SelectDateTime, + ) class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """Display a list of cash summaries""" + model = CashRegisterSummary - template_name = 'counter/cash_summary_list.jinja' + template_name = "counter/cash_summary_list.jinja" context_object_name = "cashsummary_list" current_tab = "cash_summary" - queryset = CashRegisterSummary.objects.all().order_by('-date') + queryset = CashRegisterSummary.objects.all().order_by("-date") paginate_by = settings.SITH_COUNTER_CASH_SUMMARY_LENGTH def get_context_data(self, **kwargs): """ Add sums to the context """ kwargs = super(CashSummaryListView, self).get_context_data(**kwargs) form = CashSummaryFormBase(self.request.GET) - kwargs['form'] = form - kwargs['summaries_sums'] = {} - kwargs['refilling_sums'] = {} + kwargs["form"] = form + kwargs["summaries_sums"] = {} + kwargs["refilling_sums"] = {} for c in Counter.objects.filter(type="BAR").all(): refillings = Refilling.objects.filter(counter=c) cashredistersummaries = CashRegisterSummary.objects.filter(counter=c) - if form.is_valid() and form.cleaned_data['begin_date']: - refillings = refillings.filter(date__gte=form.cleaned_data['begin_date']) - cashredistersummaries = cashredistersummaries.filter(date__gte=form.cleaned_data['begin_date']) + if form.is_valid() and form.cleaned_data["begin_date"]: + refillings = refillings.filter( + date__gte=form.cleaned_data["begin_date"] + ) + cashredistersummaries = cashredistersummaries.filter( + date__gte=form.cleaned_data["begin_date"] + ) else: - last_summary = CashRegisterSummary.objects.filter(counter=c, emptied=True).order_by('-date').first() + last_summary = ( + CashRegisterSummary.objects.filter(counter=c, emptied=True) + .order_by("-date") + .first() + ) if last_summary: refillings = refillings.filter(date__gt=last_summary.date) - cashredistersummaries = cashredistersummaries.filter(date__gt=last_summary.date) + cashredistersummaries = cashredistersummaries.filter( + date__gt=last_summary.date + ) else: - refillings = refillings.filter(date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC)) # My birth date should be old enough - cashredistersummaries = cashredistersummaries.filter(date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC)) - if form.is_valid() and form.cleaned_data['end_date']: - refillings = refillings.filter(date__lte=form.cleaned_data['end_date']) - cashredistersummaries = cashredistersummaries.filter(date__lte=form.cleaned_data['end_date']) - kwargs['summaries_sums'][c.name] = sum([s.get_total() for s in cashredistersummaries.all()]) - kwargs['refilling_sums'][c.name] = sum([s.amount for s in refillings.all()]) + refillings = refillings.filter( + date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC) + ) # My birth date should be old enough + cashredistersummaries = cashredistersummaries.filter( + date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC) + ) + if form.is_valid() and form.cleaned_data["end_date"]: + refillings = refillings.filter(date__lte=form.cleaned_data["end_date"]) + cashredistersummaries = cashredistersummaries.filter( + date__lte=form.cleaned_data["end_date"] + ) + kwargs["summaries_sums"][c.name] = sum( + [s.get_total() for s in cashredistersummaries.all()] + ) + kwargs["refilling_sums"][c.name] = sum([s.amount for s in refillings.all()]) return kwargs class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView): - template_name = 'counter/invoices_call.jinja' - current_tab = 'invoices_call' + template_name = "counter/invoices_call.jinja" + current_tab = "invoices_call" def get_context_data(self, **kwargs): """ Add sums to the context """ kwargs = super(InvoiceCallView, self).get_context_data(**kwargs) - kwargs['months'] = Selling.objects.datetimes('date', 'month', order='DESC') + kwargs["months"] = Selling.objects.datetimes("date", "month", order="DESC") start_date = None end_date = None try: - start_date = datetime.strptime(self.request.GET['month'], '%Y-%m') + start_date = datetime.strptime(self.request.GET["month"], "%Y-%m") except: - start_date = datetime(year=timezone.now().year, month=(timezone.now().month + 10) % 12 + 1, day=1) + start_date = datetime( + year=timezone.now().year, + month=(timezone.now().month + 10) % 12 + 1, + day=1, + ) start_date = start_date.replace(tzinfo=pytz.UTC) - end_date = (start_date + timedelta(days=32)).replace(day=1, hour=0, minute=0, microsecond=0) + end_date = (start_date + timedelta(days=32)).replace( + day=1, hour=0, minute=0, microsecond=0 + ) from django.db.models import Sum, Case, When, F, DecimalField - kwargs['sum_cb'] = sum([r.amount for r in Refilling.objects.filter(payment_method='CARD', is_validated=True, - date__gte=start_date, date__lte=end_date)]) - kwargs['sum_cb'] += sum([s.quantity * s.unit_price for s in Selling.objects.filter(payment_method='CARD', is_validated=True, - date__gte=start_date, date__lte=end_date)]) - kwargs['start_date'] = start_date - kwargs['sums'] = Selling.objects.values('club__name').annotate(selling_sum=Sum( - Case(When(date__gte=start_date, - date__lt=end_date, - then=F('unit_price') * F('quantity')), - output_field=CurrencyField() - ) - )).exclude(selling_sum=None).order_by('-selling_sum') + + kwargs["sum_cb"] = sum( + [ + r.amount + for r in Refilling.objects.filter( + payment_method="CARD", + is_validated=True, + date__gte=start_date, + date__lte=end_date, + ) + ] + ) + kwargs["sum_cb"] += sum( + [ + s.quantity * s.unit_price + for s in Selling.objects.filter( + payment_method="CARD", + is_validated=True, + date__gte=start_date, + date__lte=end_date, + ) + ] + ) + kwargs["start_date"] = start_date + kwargs["sums"] = ( + Selling.objects.values("club__name") + .annotate( + selling_sum=Sum( + Case( + When( + date__gte=start_date, + date__lt=end_date, + then=F("unit_price") * F("quantity"), + ), + output_field=CurrencyField(), + ) + ) + ) + .exclude(selling_sum=None) + .order_by("-selling_sum") + ) return kwargs @@ -1172,28 +1618,31 @@ class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """ A list view for the admins """ + model = Eticket - template_name = 'counter/eticket_list.jinja' - ordering = ['id'] + template_name = "counter/eticket_list.jinja" + ordering = ["id"] current_tab = "etickets" class EticketForm(forms.ModelForm): class Meta: model = Eticket - fields = ['product', 'banner', 'event_title', 'event_date'] - widgets = { - 'event_date': SelectDate, - } - product = AutoCompleteSelectField('products', show_help_text=False, label=_("Product"), required=True) + fields = ["product", "banner", "event_title", "event_date"] + widgets = {"event_date": SelectDate} + + product = AutoCompleteSelectField( + "products", show_help_text=False, label=_("Product"), required=True + ) class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """ Create an eticket """ + model = Eticket - template_name = 'core/create.jinja' + template_name = "core/create.jinja" form_class = EticketForm current_tab = "etickets" @@ -1202,8 +1651,9 @@ class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ Edit an eticket """ + model = Eticket - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" form_class = EticketForm pk_url_kwarg = "eticket_id" current_tab = "etickets" @@ -1213,6 +1663,7 @@ class EticketPDFView(CanViewMixin, DetailView): """ Display the PDF of an eticket """ + model = Selling pk_url_kwarg = "selling_id" @@ -1223,13 +1674,19 @@ class EticketPDFView(CanViewMixin, DetailView): from reportlab.graphics.shapes import Drawing from reportlab.graphics.barcode.qr import QrCodeWidget from reportlab.graphics import renderPDF + self.object = self.get_object() eticket = self.object.product.eticket user = self.object.customer.user - code = "%s %s %s %s" % (self.object.customer.user.id, self.object.product.id, self.object.id, self.object.quantity) + code = "%s %s %s %s" % ( + self.object.customer.user.id, + self.object.product.id, + self.object.id, + self.object.quantity, + ) code += " " + eticket.get_hash(code)[:8].upper() - response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = 'filename="eticket.pdf"' + response = HttpResponse(content_type="application/pdf") + response["Content-Disposition"] = 'filename="eticket.pdf"' p = canvas.Canvas(response) p.setTitle("Eticket") im = ImageReader("core/static/core/img/eticket.jpg") @@ -1257,15 +1714,22 @@ class EticketPDFView(CanViewMixin, DetailView): p.drawCentredString(10.5 * cm, 23.6 * cm, eticket.event_title) if eticket.event_date: p.setFont("Helvetica-Bold", 16) - p.drawCentredString(10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y")) # FIXME with a locale + p.drawCentredString( + 10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y") + ) # FIXME with a locale p.setFont("Helvetica-Bold", 14) - p.drawCentredString(10.5 * cm, 15 * cm, "%s : %d %s" % (user.get_display_name(), self.object.quantity, str(_("people(s)")))) + p.drawCentredString( + 10.5 * cm, + 15 * cm, + "%s : %d %s" + % (user.get_display_name(), self.object.quantity, str(_("people(s)"))), + ) p.setFont("Courier-Bold", 14) qrcode = QrCodeWidget(code) bounds = qrcode.getBounds() width = bounds[2] - bounds[0] height = bounds[3] - bounds[1] - d = Drawing(260, 260, transform=[260. / width, 0, 0, 260. / height, 0, 0]) + d = Drawing(260, 260, transform=[260.0 / width, 0, 0, 260.0 / height, 0, 0]) d.add(qrcode) renderPDF.draw(d, p, 10.5 * cm - 130, 6.1 * cm) p.drawCentredString(10.5 * cm, 6 * cm, code) @@ -1286,17 +1750,18 @@ class CounterRefillingListView(CounterAdminTabsMixin, CounterAdminMixin, ListVie """ List of refillings on a counter """ + model = Refilling - template_name = 'counter/refilling_list.jinja' + template_name = "counter/refilling_list.jinja" current_tab = "counters" paginate_by = 30 def dispatch(self, request, *args, **kwargs): - self.counter = get_object_or_404(Counter, pk=kwargs['counter_id']) + self.counter = get_object_or_404(Counter, pk=kwargs["counter_id"]) self.queryset = Refilling.objects.filter(counter__id=self.counter.id) return super(CounterRefillingListView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(CounterRefillingListView, self).get_context_data(**kwargs) - kwargs['counter'] = self.counter + kwargs["counter"] = self.counter return kwargs diff --git a/eboutic/__init__.py b/eboutic/__init__.py index 0a9419f8..0ace29c4 100644 --- a/eboutic/__init__.py +++ b/eboutic/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/eboutic/migrations/0001_initial.py b/eboutic/migrations/0001_initial.py index 820f5344..2eadf88c 100644 --- a/eboutic/migrations/0001_initial.py +++ b/eboutic/migrations/0001_initial.py @@ -8,56 +8,127 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] + dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] operations = [ migrations.CreateModel( - name='Basket', + name="Basket", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('date', models.DateTimeField(verbose_name='date', auto_now=True)), - ('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, related_name='baskets')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("date", models.DateTimeField(verbose_name="date", auto_now=True)), + ( + "user", + models.ForeignKey( + verbose_name="user", + to=settings.AUTH_USER_MODEL, + related_name="baskets", + ), + ), ], ), migrations.CreateModel( - name='BasketItem', + name="BasketItem", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('product_id', models.IntegerField(verbose_name='product id')), - ('product_name', models.CharField(max_length=255, verbose_name='product name')), - ('type_id', models.IntegerField(verbose_name='product type id')), - ('product_unit_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='unit price')), - ('quantity', models.IntegerField(verbose_name='quantity')), - ('basket', models.ForeignKey(verbose_name='basket', to='eboutic.Basket', related_name='items')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("product_id", models.IntegerField(verbose_name="product id")), + ( + "product_name", + models.CharField(max_length=255, verbose_name="product name"), + ), + ("type_id", models.IntegerField(verbose_name="product type id")), + ( + "product_unit_price", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="unit price" + ), + ), + ("quantity", models.IntegerField(verbose_name="quantity")), + ( + "basket", + models.ForeignKey( + verbose_name="basket", to="eboutic.Basket", related_name="items" + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, ), migrations.CreateModel( - name='Invoice', + name="Invoice", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('date', models.DateTimeField(verbose_name='date', auto_now=True)), - ('validated', models.BooleanField(verbose_name='validated', default=False)), - ('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, related_name='invoices')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("date", models.DateTimeField(verbose_name="date", auto_now=True)), + ( + "validated", + models.BooleanField(verbose_name="validated", default=False), + ), + ( + "user", + models.ForeignKey( + verbose_name="user", + to=settings.AUTH_USER_MODEL, + related_name="invoices", + ), + ), ], ), migrations.CreateModel( - name='InvoiceItem', + name="InvoiceItem", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('product_id', models.IntegerField(verbose_name='product id')), - ('product_name', models.CharField(max_length=255, verbose_name='product name')), - ('type_id', models.IntegerField(verbose_name='product type id')), - ('product_unit_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='unit price')), - ('quantity', models.IntegerField(verbose_name='quantity')), - ('invoice', models.ForeignKey(verbose_name='invoice', to='eboutic.Invoice', related_name='items')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("product_id", models.IntegerField(verbose_name="product id")), + ( + "product_name", + models.CharField(max_length=255, verbose_name="product name"), + ), + ("type_id", models.IntegerField(verbose_name="product type id")), + ( + "product_unit_price", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="unit price" + ), + ), + ("quantity", models.IntegerField(verbose_name="quantity")), + ( + "invoice", + models.ForeignKey( + verbose_name="invoice", + to="eboutic.Invoice", + related_name="items", + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, ), ] diff --git a/eboutic/models.py b/eboutic/models.py index 3a61da9d..0a157afa 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -35,14 +35,23 @@ class Basket(models.Model): """ Basket is built when the user connects to an eboutic page """ - user = models.ForeignKey(User, related_name='baskets', verbose_name=_('user'), blank=False) - date = models.DateTimeField(_('date'), auto_now=True) + + user = models.ForeignKey( + User, related_name="baskets", verbose_name=_("user"), blank=False + ) + date = models.DateTimeField(_("date"), auto_now=True) def add_product(self, p, q=1): item = self.items.filter(product_id=p.id).first() if item is None: - BasketItem(basket=self, product_id=p.id, product_name=p.name, type_id=p.product_type.id, - quantity=q, product_unit_price=p.selling_price).save() + BasketItem( + basket=self, + product_id=p.id, + product_name=p.name, + type_id=p.product_type.id, + quantity=q, + product_unit_price=p.selling_price, + ).save() else: item.quantity += q item.save() @@ -69,8 +78,11 @@ class Invoice(models.Model): """ Invoices are generated once the payment has been validated """ - user = models.ForeignKey(User, related_name='invoices', verbose_name=_('user'), blank=False) - date = models.DateTimeField(_('date'), auto_now=True) + + user = models.ForeignKey( + User, related_name="invoices", verbose_name=_("user"), blank=False + ) + date = models.DateTimeField(_("date"), auto_now=True) validated = models.BooleanField(_("validated"), default=False) def __str__(self): @@ -86,9 +98,14 @@ class Invoice(models.Model): if self.validated: raise DataError(_("Invoice already validated")) from counter.models import Customer + if not Customer.objects.filter(user=self.user).exists(): number = Customer.objects.count() + 1 - Customer(user=self.user, account_id=Customer.generate_account_id(number), amount=0).save() + Customer( + user=self.user, + account_id=Customer.generate_account_id(number), + amount=0, + ).save() eboutic = Counter.objects.filter(type="EBOUTIC").first() for i in self.items.all(): if i.type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING: @@ -123,22 +140,28 @@ class Invoice(models.Model): class AbstractBaseItem(models.Model): - product_id = models.IntegerField(_('product id')) - product_name = models.CharField(_('product name'), max_length=255) - type_id = models.IntegerField(_('product type id')) - product_unit_price = CurrencyField(_('unit price')) - quantity = models.IntegerField(_('quantity')) + product_id = models.IntegerField(_("product id")) + product_name = models.CharField(_("product name"), max_length=255) + type_id = models.IntegerField(_("product type id")) + product_unit_price = CurrencyField(_("unit price")) + quantity = models.IntegerField(_("quantity")) class Meta: abstract = True def __str__(self): - return "Item: %s (%s) x%d" % (self.product_name, self.product_unit_price, self.quantity) + return "Item: %s (%s) x%d" % ( + self.product_name, + self.product_unit_price, + self.quantity, + ) class BasketItem(AbstractBaseItem): - basket = models.ForeignKey(Basket, related_name='items', verbose_name=_('basket')) + basket = models.ForeignKey(Basket, related_name="items", verbose_name=_("basket")) class InvoiceItem(AbstractBaseItem): - invoice = models.ForeignKey(Invoice, related_name='items', verbose_name=_('invoice')) + invoice = models.ForeignKey( + Invoice, related_name="items", verbose_name=_("invoice") + ) diff --git a/eboutic/tests.py b/eboutic/tests.py index a2dc4875..0a727950 100644 --- a/eboutic/tests.py +++ b/eboutic/tests.py @@ -62,112 +62,193 @@ class EbouticTest(TestCase): sig = crypto.sign(privkey, query, "sha1") b64sig = base64.b64encode(sig).decode("ascii") - url = reverse("eboutic:etransation_autoanswer") + "?%s&Sig=%s" % (query, urllib.parse.quote_plus(b64sig)) + url = reverse("eboutic:etransation_autoanswer") + "?%s&Sig=%s" % ( + query, + urllib.parse.quote_plus(b64sig), + ) response = self.client.get(url) self.assertTrue(response.status_code == 200) self.assertTrue(response.content.decode("utf-8") == "") return response def test_buy_simple_product_with_sith_account(self): - self.client.login(username='subscriber', password='plop') - Refilling(amount=10, counter=self.eboutic, operator=self.skia, customer=self.subscriber.customer).save() - response = self.client.post(reverse("eboutic:main"), { - "action": "add_product", - "product_id": self.barbar.id}) - self.assertTrue("\\n" - " \\n" - "\\n Barbar: 1.70 \\xe2\\x82\\xac" in str(response.content)) + self.client.login(username="subscriber", password="plop") + Refilling( + amount=10, + counter=self.eboutic, + operator=self.skia, + customer=self.subscriber.customer, + ).save() + response = self.client.post( + reverse("eboutic:main"), + {"action": "add_product", "product_id": self.barbar.id}, + ) + self.assertTrue( + '\\n' + ' \\n' + "\\n Barbar: 1.70 \\xe2\\x82\\xac" in str(response.content) + ) response = self.client.post(reverse("eboutic:command")) - self.assertTrue("\\n Barbar\\n 1\\n" - " 1.70 \\xe2\\x82\\xac\\n " in str(response.content)) - response = self.client.post(reverse("eboutic:pay_with_sith"), { - "action": "pay_with_sith_account" - }) - self.assertTrue("Le paiement a \\xc3\\xa9t\\xc3\\xa9 effectu\\xc3\\xa9\\n" in str(response.content)) - response = self.client.get(reverse("core:user_account_detail", kwargs={ - "user_id": self.subscriber.id, - "year": datetime.now().year, - "month": datetime.now().month, - })) - self.assertTrue("class=\"selected_tab\">Compte (8.30 \\xe2\\x82\\xac)" in str(response.content)) - self.assertTrue("Eboutic\\n Subscribed User\\n" - " Barbar\\n 1\\n 1.70 \\xe2\\x82\\xac\\n" - " Compte utilisateur" in str(response.content)) + self.assertTrue( + "\\n Barbar\\n 1\\n" + " 1.70 \\xe2\\x82\\xac\\n " + in str(response.content) + ) + response = self.client.post( + reverse("eboutic:pay_with_sith"), {"action": "pay_with_sith_account"} + ) + self.assertTrue( + "Le paiement a \\xc3\\xa9t\\xc3\\xa9 effectu\\xc3\\xa9\\n" + in str(response.content) + ) + response = self.client.get( + reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.subscriber.id, + "year": datetime.now().year, + "month": datetime.now().month, + }, + ) + ) + self.assertTrue( + 'class="selected_tab">Compte (8.30 \\xe2\\x82\\xac)' + in str(response.content) + ) + self.assertTrue( + 'Eboutic\\n Subscribed User\\n' + " Barbar\\n 1\\n 1.70 \\xe2\\x82\\xac\\n" + " Compte utilisateur" in str(response.content) + ) def test_buy_simple_product_with_credit_card(self): - self.client.login(username='subscriber', password='plop') - response = self.client.post(reverse("eboutic:main"), { - "action": "add_product", - "product_id": self.barbar.id}) - self.assertTrue("\\n" - " \\n" - "\\n Barbar: 1.70 \\xe2\\x82\\xac" in str(response.content)) + self.client.login(username="subscriber", password="plop") + response = self.client.post( + reverse("eboutic:main"), + {"action": "add_product", "product_id": self.barbar.id}, + ) + self.assertTrue( + '\\n' + ' \\n' + "\\n Barbar: 1.70 \\xe2\\x82\\xac" in str(response.content) + ) response = self.client.post(reverse("eboutic:command")) - self.assertTrue("\\n Barbar\\n 1\\n" - " 1.70 \\xe2\\x82\\xac\\n " in str(response.content)) + self.assertTrue( + "\\n Barbar\\n 1\\n" + " 1.70 \\xe2\\x82\\xac\\n " + in str(response.content) + ) response = self.generate_bank_valid_answer_from_page_content(response.content) - response = self.client.get(reverse("core:user_account_detail", kwargs={ - "user_id": self.subscriber.id, - "year": datetime.now().year, - "month": datetime.now().month, - })) - self.assertTrue("class=\"selected_tab\">Compte (0.00 \\xe2\\x82\\xac)" in str(response.content)) - self.assertTrue("Eboutic\\n Subscribed User\\n" - " Barbar\\n 1\\n 1.70 \\xe2\\x82\\xac\\n" - " Carte bancaire" in str(response.content)) + response = self.client.get( + reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.subscriber.id, + "year": datetime.now().year, + "month": datetime.now().month, + }, + ) + ) + self.assertTrue( + 'class="selected_tab">Compte (0.00 \\xe2\\x82\\xac)' + in str(response.content) + ) + self.assertTrue( + 'Eboutic\\n Subscribed User\\n' + " Barbar\\n 1\\n 1.70 \\xe2\\x82\\xac\\n" + " Carte bancaire" in str(response.content) + ) def test_buy_refill_product_with_credit_card(self): - self.client.login(username='subscriber', password='plop') - response = self.client.post(reverse("eboutic:main"), { - "action": "add_product", - "product_id": self.refill.id}) - self.assertTrue("\\n" - " \\n" - "\\n Rechargement 15 \\xe2\\x82\\xac: 15.00 \\xe2\\x82\\xac" in str(response.content)) + self.client.login(username="subscriber", password="plop") + response = self.client.post( + reverse("eboutic:main"), + {"action": "add_product", "product_id": self.refill.id}, + ) + self.assertTrue( + '\\n' + ' \\n' + "\\n Rechargement 15 \\xe2\\x82\\xac: 15.00 \\xe2\\x82\\xac" + in str(response.content) + ) response = self.client.post(reverse("eboutic:command")) - self.assertTrue("\\n Rechargement 15 \\xe2\\x82\\xac\\n 1\\n" - " 15.00 \\xe2\\x82\\xac\\n " in str(response.content)) + self.assertTrue( + "\\n Rechargement 15 \\xe2\\x82\\xac\\n 1\\n" + " 15.00 \\xe2\\x82\\xac\\n " + in str(response.content) + ) response = self.generate_bank_valid_answer_from_page_content(response.content) - response = self.client.get(reverse("core:user_account_detail", kwargs={ - "user_id": self.subscriber.id, - "year": datetime.now().year, - "month": datetime.now().month, - })) - self.assertTrue("class=\"selected_tab\">Compte (15.00 \\xe2\\x82\\xac)" in str(response.content)) - self.assertTrue("\\n
      \\n \\n " - "
    • 1 x Rechargement 15 \\xe2\\x82\\xac - 15.00 \\xe2\\x82\\xac
    • \\n" - " \\n
    \\n \\n" - " 15.00 \\xe2\\x82\\xac" in str(response.content)) + response = self.client.get( + reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.subscriber.id, + "year": datetime.now().year, + "month": datetime.now().month, + }, + ) + ) + self.assertTrue( + 'class="selected_tab">Compte (15.00 \\xe2\\x82\\xac)' + in str(response.content) + ) + self.assertTrue( + "\\n
      \\n \\n " + "
    • 1 x Rechargement 15 \\xe2\\x82\\xac - 15.00 \\xe2\\x82\\xac
    • \\n" + " \\n
    \\n \\n" + " 15.00 \\xe2\\x82\\xac" in str(response.content) + ) def test_buy_subscribe_product_with_credit_card(self): - self.client.login(username='old_subscriber', password='plop') - response = self.client.get(reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id})) + self.client.login(username="old_subscriber", password="plop") + response = self.client.get( + reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id}) + ) self.assertTrue("Non cotisant" in str(response.content)) - response = self.client.post(reverse("eboutic:main"), { - "action": "add_product", - "product_id": self.cotis.id}) - self.assertTrue("\\n" - " \\n" - "\\n Cotis 1 semestre: 15.00 \\xe2\\x82\\xac" in str(response.content)) + response = self.client.post( + reverse("eboutic:main"), + {"action": "add_product", "product_id": self.cotis.id}, + ) + self.assertTrue( + '\\n' + ' \\n' + "\\n Cotis 1 semestre: 15.00 \\xe2\\x82\\xac" + in str(response.content) + ) response = self.client.post(reverse("eboutic:command")) - self.assertTrue("\\n Cotis 1 semestre\\n 1\\n" - " 15.00 \\xe2\\x82\\xac\\n " in str(response.content)) + self.assertTrue( + "\\n Cotis 1 semestre\\n 1\\n" + " 15.00 \\xe2\\x82\\xac\\n " + in str(response.content) + ) response = self.generate_bank_valid_answer_from_page_content(response.content) - response = self.client.get(reverse("core:user_account_detail", kwargs={ - "user_id": self.old_subscriber.id, - "year": datetime.now().year, - "month": datetime.now().month, - })) - self.assertTrue("class=\"selected_tab\">Compte (0.00 \\xe2\\x82\\xac)" in str(response.content)) - self.assertTrue("\\n
      \\n \\n " - "
    • 1 x Cotis 1 semestre - 15.00 \\xe2\\x82\\xac
    • \\n" - " \\n
    \\n \\n" - " 15.00 \\xe2\\x82\\xac" in str(response.content)) - response = self.client.get(reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id})) + response = self.client.get( + reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.old_subscriber.id, + "year": datetime.now().year, + "month": datetime.now().month, + }, + ) + ) + self.assertTrue( + 'class="selected_tab">Compte (0.00 \\xe2\\x82\\xac)' + in str(response.content) + ) + self.assertTrue( + "\\n
      \\n \\n " + "
    • 1 x Cotis 1 semestre - 15.00 \\xe2\\x82\\xac
    • \\n" + " \\n
    \\n \\n" + " 15.00 \\xe2\\x82\\xac" in str(response.content) + ) + response = self.client.get( + reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id}) + ) self.assertTrue("Cotisant jusqu\\'au" in str(response.content)) diff --git a/eboutic/tests/test.py b/eboutic/tests/test.py index 38be7646..83a14ad9 100755 --- a/eboutic/tests/test.py +++ b/eboutic/tests/test.py @@ -32,7 +32,3 @@ try: print("Verify OK") except: print("Verify failed") - - - - diff --git a/eboutic/urls.py b/eboutic/urls.py index d4ddb486..4ee79f53 100644 --- a/eboutic/urls.py +++ b/eboutic/urls.py @@ -28,8 +28,12 @@ from eboutic.views import * urlpatterns = [ # Subscription views - url(r'^$', EbouticMain.as_view(), name='main'), - url(r'^command$', EbouticCommand.as_view(), name='command'), - url(r'^pay$', EbouticPayWithSith.as_view(), name='pay_with_sith'), - url(r'^et_autoanswer$', EtransactionAutoAnswer.as_view(), name='etransation_autoanswer'), + url(r"^$", EbouticMain.as_view(), name="main"), + url(r"^command$", EbouticCommand.as_view(), name="command"), + url(r"^pay$", EbouticPayWithSith.as_view(), name="pay_with_sith"), + url( + r"^et_autoanswer$", + EtransactionAutoAnswer.as_view(), + name="etransation_autoanswer", + ), ] diff --git a/eboutic/views.py b/eboutic/views.py index 7d841d26..56017237 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -41,44 +41,50 @@ from eboutic.models import Basket, Invoice, InvoiceItem class EbouticMain(TemplateView): - template_name = 'eboutic/eboutic_main.jinja' + template_name = "eboutic/eboutic_main.jinja" def make_basket(self, request): - if 'basket_id' not in request.session.keys(): # Init the basket session entry + if "basket_id" not in request.session.keys(): # Init the basket session entry self.basket = Basket(user=request.user) self.basket.save() else: - self.basket = Basket.objects.filter(id=request.session['basket_id']).first() + self.basket = Basket.objects.filter(id=request.session["basket_id"]).first() if self.basket is None: self.basket = Basket(user=request.user) self.basket.save() - request.session['basket_id'] = self.basket.id + request.session["basket_id"] = self.basket.id request.session.modified = True def get(self, request, *args, **kwargs): if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) + return HttpResponseRedirect( + reverse_lazy("core:login", args=self.args, kwargs=kwargs) + + "?next=" + + request.path + ) self.object = Counter.objects.filter(type="EBOUTIC").first() self.make_basket(request) return super(EbouticMain, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) + return HttpResponseRedirect( + reverse_lazy("core:login", args=self.args, kwargs=kwargs) + + "?next=" + + request.path + ) self.object = Counter.objects.filter(type="EBOUTIC").first() self.make_basket(request) - if 'add_product' in request.POST['action']: + if "add_product" in request.POST["action"]: self.add_product(request) - elif 'del_product' in request.POST['action']: + elif "del_product" in request.POST["action"]: self.del_product(request) return self.render_to_response(self.get_context_data(**kwargs)) def add_product(self, request): """ Add a product to the basket """ try: - p = self.object.products.filter(id=int(request.POST['product_id'])).first() + p = self.object.products.filter(id=int(request.POST["product_id"])).first() if not p.buying_groups.exists(): self.basket.add_product(p) for g in p.buying_groups.all(): @@ -91,79 +97,122 @@ class EbouticMain(TemplateView): def del_product(self, request): """ Delete a product from the basket """ try: - p = self.object.products.filter(id=int(request.POST['product_id'])).first() + p = self.object.products.filter(id=int(request.POST["product_id"])).first() self.basket.del_product(p) except: pass def get_context_data(self, **kwargs): kwargs = super(EbouticMain, self).get_context_data(**kwargs) - kwargs['basket'] = self.basket - kwargs['eboutic'] = Counter.objects.filter(type="EBOUTIC").first() - kwargs['categories'] = ProductType.objects.all() + kwargs["basket"] = self.basket + kwargs["eboutic"] = Counter.objects.filter(type="EBOUTIC").first() + kwargs["categories"] = ProductType.objects.all() if not self.request.user.was_subscribed: - kwargs['categories'] = kwargs['categories'].exclude(id=settings.SITH_PRODUCTTYPE_SUBSCRIPTION) + kwargs["categories"] = kwargs["categories"].exclude( + id=settings.SITH_PRODUCTTYPE_SUBSCRIPTION + ) return kwargs class EbouticCommand(TemplateView): - template_name = 'eboutic/eboutic_makecommand.jinja' + template_name = "eboutic/eboutic_makecommand.jinja" def get(self, request, *args, **kwargs): if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) + return HttpResponseRedirect( + reverse_lazy("core:login", args=self.args, kwargs=kwargs) + + "?next=" + + request.path + ) + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) def post(self, request, *args, **kwargs): if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) - if 'basket_id' not in request.session.keys(): - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) - self.basket = Basket.objects.filter(id=request.session['basket_id']).first() + return HttpResponseRedirect( + reverse_lazy("core:login", args=self.args, kwargs=kwargs) + + "?next=" + + request.path + ) + if "basket_id" not in request.session.keys(): + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) + self.basket = Basket.objects.filter(id=request.session["basket_id"]).first() if self.basket is None: - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) else: - kwargs['basket'] = self.basket + kwargs["basket"] = self.basket return self.render_to_response(self.get_context_data(**kwargs)) def get_context_data(self, **kwargs): kwargs = super(EbouticCommand, self).get_context_data(**kwargs) - kwargs['et_request'] = OrderedDict() - kwargs['et_request']['PBX_SITE'] = settings.SITH_EBOUTIC_PBX_SITE - kwargs['et_request']['PBX_RANG'] = settings.SITH_EBOUTIC_PBX_RANG - kwargs['et_request']['PBX_IDENTIFIANT'] = settings.SITH_EBOUTIC_PBX_IDENTIFIANT - kwargs['et_request']['PBX_TOTAL'] = int(self.basket.get_total() * 100) - kwargs['et_request']['PBX_DEVISE'] = 978 # This is Euro. ET support only this value anyway - kwargs['et_request']['PBX_CMD'] = self.basket.id - kwargs['et_request']['PBX_PORTEUR'] = self.basket.user.email - kwargs['et_request']['PBX_RETOUR'] = "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K" - kwargs['et_request']['PBX_HASH'] = "SHA512" - kwargs['et_request']['PBX_TYPEPAIEMENT'] = "CARTE" - kwargs['et_request']['PBX_TYPECARTE'] = "CB" - kwargs['et_request']['PBX_TIME'] = str(datetime.now().replace(microsecond=0).isoformat('T')) - kwargs['et_request']['PBX_HMAC'] = hmac.new(settings.SITH_EBOUTIC_HMAC_KEY, - bytes("&".join(["%s=%s" % (k, v) for k, v in kwargs['et_request'].items()]), 'utf-8'), - "sha512").hexdigest().upper() + kwargs["et_request"] = OrderedDict() + kwargs["et_request"]["PBX_SITE"] = settings.SITH_EBOUTIC_PBX_SITE + kwargs["et_request"]["PBX_RANG"] = settings.SITH_EBOUTIC_PBX_RANG + kwargs["et_request"]["PBX_IDENTIFIANT"] = settings.SITH_EBOUTIC_PBX_IDENTIFIANT + kwargs["et_request"]["PBX_TOTAL"] = int(self.basket.get_total() * 100) + kwargs["et_request"][ + "PBX_DEVISE" + ] = 978 # This is Euro. ET support only this value anyway + kwargs["et_request"]["PBX_CMD"] = self.basket.id + kwargs["et_request"]["PBX_PORTEUR"] = self.basket.user.email + kwargs["et_request"]["PBX_RETOUR"] = "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K" + kwargs["et_request"]["PBX_HASH"] = "SHA512" + kwargs["et_request"]["PBX_TYPEPAIEMENT"] = "CARTE" + kwargs["et_request"]["PBX_TYPECARTE"] = "CB" + kwargs["et_request"]["PBX_TIME"] = str( + datetime.now().replace(microsecond=0).isoformat("T") + ) + kwargs["et_request"]["PBX_HMAC"] = ( + hmac.new( + settings.SITH_EBOUTIC_HMAC_KEY, + bytes( + "&".join( + ["%s=%s" % (k, v) for k, v in kwargs["et_request"].items()] + ), + "utf-8", + ), + "sha512", + ) + .hexdigest() + .upper() + ) return kwargs class EbouticPayWithSith(TemplateView): - template_name = 'eboutic/eboutic_payment_result.jinja' + template_name = "eboutic/eboutic_payment_result.jinja" def post(self, request, *args, **kwargs): try: with transaction.atomic(): - if 'basket_id' not in request.session.keys() or not request.user.is_authenticated(): - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) - b = Basket.objects.filter(id=request.session['basket_id']).first() - if b is None or b.items.filter(type_id=settings.SITH_COUNTER_PRODUCTTYPE_REFILLING).exists(): - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) + if ( + "basket_id" not in request.session.keys() + or not request.user.is_authenticated() + ): + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) + b = Basket.objects.filter(id=request.session["basket_id"]).first() + if ( + b is None + or b.items.filter( + type_id=settings.SITH_COUNTER_PRODUCTTYPE_REFILLING + ).exists() + ): + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) c = Customer.objects.filter(user__id=b.user.id).first() if c is None: - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) - kwargs['not_enough'] = True + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) + kwargs["not_enough"] = True if c.amount < b.get_total(): raise DataError(_("You do not have enough money to buy the basket")) else: @@ -182,33 +231,44 @@ class EbouticPayWithSith(TemplateView): payment_method="SITH_ACCOUNT", ).save() b.delete() - kwargs['not_enough'] = False - request.session.pop('basket_id', None) + kwargs["not_enough"] = False + request.session.pop("basket_id", None) except DataError as e: - kwargs['not_enough'] = True + kwargs["not_enough"] = True return self.render_to_response(self.get_context_data(**kwargs)) class EtransactionAutoAnswer(View): def get(self, request, *args, **kwargs): - if (not 'Amount' in request.GET.keys() or - not 'BasketID' in request.GET.keys() or - not 'Auto' in request.GET.keys() or - not 'Error' in request.GET.keys() or - not 'Sig' in request.GET.keys()): + if ( + not "Amount" in request.GET.keys() + or not "BasketID" in request.GET.keys() + or not "Auto" in request.GET.keys() + or not "Error" in request.GET.keys() + or not "Sig" in request.GET.keys() + ): return HttpResponse("Bad arguments", status=400) key = crypto.load_publickey(crypto.FILETYPE_PEM, settings.SITH_EBOUTIC_PUB_KEY) cert = crypto.X509() cert.set_pubkey(key) - sig = base64.b64decode(request.GET['Sig']) + sig = base64.b64decode(request.GET["Sig"]) try: - crypto.verify(cert, sig, '&'.join(request.META['QUERY_STRING'].split('&')[:-1]), "sha1") + crypto.verify( + cert, + sig, + "&".join(request.META["QUERY_STRING"].split("&")[:-1]), + "sha1", + ) except: return HttpResponse("Bad signature", status=400) - if request.GET['Error'] == "00000": + if request.GET["Error"] == "00000": try: with transaction.atomic(): - b = Basket.objects.select_for_update().filter(id=request.GET['BasketID']).first() + b = ( + Basket.objects.select_for_update() + .filter(id=request.GET["BasketID"]) + .first() + ) if b is None: raise SuspiciousOperation("Basket does not exists") i = Invoice() @@ -216,12 +276,20 @@ class EtransactionAutoAnswer(View): i.payment_method = "CARD" i.save() for it in b.items.all(): - InvoiceItem(invoice=i, product_id=it.product_id, product_name=it.product_name, type_id=it.type_id, - product_unit_price=it.product_unit_price, quantity=it.quantity).save() + InvoiceItem( + invoice=i, + product_id=it.product_id, + product_name=it.product_name, + type_id=it.type_id, + product_unit_price=it.product_unit_price, + quantity=it.quantity, + ).save() i.validate() b.delete() except Exception as e: return HttpResponse("Payment failed with error: " + repr(e), status=400) return HttpResponse() else: - return HttpResponse("Payment failed with error: " + request.GET['Error'], status=400) + return HttpResponse( + "Payment failed with error: " + request.GET["Error"], status=400 + ) diff --git a/election/migrations/0001_initial.py b/election/migrations/0001_initial.py index 706d0aa9..00e15cfa 100644 --- a/election/migrations/0001_initial.py +++ b/election/migrations/0001_initial.py @@ -8,74 +8,208 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('core', '0018_auto_20161224_0211'), + ("core", "0018_auto_20161224_0211"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Candidature', + name="Candidature", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('program', models.TextField(null=True, verbose_name='description', blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + serialize=False, + auto_created=True, + ), + ), + ( + "program", + models.TextField(null=True, verbose_name="description", blank=True), + ), ], ), migrations.CreateModel( - name='Election', + name="Election", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('title', models.CharField(max_length=255, verbose_name='title')), - ('description', models.TextField(null=True, verbose_name='description', blank=True)), - ('start_candidature', models.DateTimeField(verbose_name='start candidature')), - ('end_candidature', models.DateTimeField(verbose_name='end candidature')), - ('start_date', models.DateTimeField(verbose_name='start date')), - ('end_date', models.DateTimeField(verbose_name='end date')), - ('candidature_groups', models.ManyToManyField(related_name='candidate_elections', verbose_name='candidature groups', blank=True, to='core.Group')), - ('edit_groups', models.ManyToManyField(related_name='editable_elections', verbose_name='edit groups', blank=True, to='core.Group')), - ('view_groups', models.ManyToManyField(related_name='viewable_elections', verbose_name='view groups', blank=True, to='core.Group')), - ('vote_groups', models.ManyToManyField(related_name='votable_elections', verbose_name='vote groups', blank=True, to='core.Group')), - ('voters', models.ManyToManyField(related_name='voted_elections', verbose_name='voters', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + serialize=False, + auto_created=True, + ), + ), + ("title", models.CharField(max_length=255, verbose_name="title")), + ( + "description", + models.TextField(null=True, verbose_name="description", blank=True), + ), + ( + "start_candidature", + models.DateTimeField(verbose_name="start candidature"), + ), + ( + "end_candidature", + models.DateTimeField(verbose_name="end candidature"), + ), + ("start_date", models.DateTimeField(verbose_name="start date")), + ("end_date", models.DateTimeField(verbose_name="end date")), + ( + "candidature_groups", + models.ManyToManyField( + related_name="candidate_elections", + verbose_name="candidature groups", + blank=True, + to="core.Group", + ), + ), + ( + "edit_groups", + models.ManyToManyField( + related_name="editable_elections", + verbose_name="edit groups", + blank=True, + to="core.Group", + ), + ), + ( + "view_groups", + models.ManyToManyField( + related_name="viewable_elections", + verbose_name="view groups", + blank=True, + to="core.Group", + ), + ), + ( + "vote_groups", + models.ManyToManyField( + related_name="votable_elections", + verbose_name="vote groups", + blank=True, + to="core.Group", + ), + ), + ( + "voters", + models.ManyToManyField( + related_name="voted_elections", + verbose_name="voters", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='ElectionList', + name="ElectionList", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('title', models.CharField(max_length=255, verbose_name='title')), - ('election', models.ForeignKey(verbose_name='election', to='election.Election', related_name='election_lists')), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + serialize=False, + auto_created=True, + ), + ), + ("title", models.CharField(max_length=255, verbose_name="title")), + ( + "election", + models.ForeignKey( + verbose_name="election", + to="election.Election", + related_name="election_lists", + ), + ), ], ), migrations.CreateModel( - name='Role', + name="Role", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('title', models.CharField(max_length=255, verbose_name='title')), - ('description', models.TextField(null=True, verbose_name='description', blank=True)), - ('max_choice', models.IntegerField(verbose_name='max choice', default=1)), - ('election', models.ForeignKey(verbose_name='election', to='election.Election', related_name='roles')), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + serialize=False, + auto_created=True, + ), + ), + ("title", models.CharField(max_length=255, verbose_name="title")), + ( + "description", + models.TextField(null=True, verbose_name="description", blank=True), + ), + ( + "max_choice", + models.IntegerField(verbose_name="max choice", default=1), + ), + ( + "election", + models.ForeignKey( + verbose_name="election", + to="election.Election", + related_name="roles", + ), + ), ], ), migrations.CreateModel( - name='Vote', + name="Vote", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('candidature', models.ManyToManyField(related_name='votes', verbose_name='candidature', to='election.Candidature')), - ('role', models.ForeignKey(verbose_name='role', to='election.Role', related_name='votes')), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + serialize=False, + auto_created=True, + ), + ), + ( + "candidature", + models.ManyToManyField( + related_name="votes", + verbose_name="candidature", + to="election.Candidature", + ), + ), + ( + "role", + models.ForeignKey( + verbose_name="role", to="election.Role", related_name="votes" + ), + ), ], ), migrations.AddField( - model_name='candidature', - name='election_list', - field=models.ForeignKey(verbose_name='election list', to='election.ElectionList', related_name='candidatures'), + model_name="candidature", + name="election_list", + field=models.ForeignKey( + verbose_name="election list", + to="election.ElectionList", + related_name="candidatures", + ), ), migrations.AddField( - model_name='candidature', - name='role', - field=models.ForeignKey(verbose_name='role', to='election.Role', related_name='candidatures'), + model_name="candidature", + name="role", + field=models.ForeignKey( + verbose_name="role", to="election.Role", related_name="candidatures" + ), ), migrations.AddField( - model_name='candidature', - name='user', - field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, related_name='candidates', blank=True), + model_name="candidature", + name="user", + field=models.ForeignKey( + verbose_name="user", + to=settings.AUTH_USER_MODEL, + related_name="candidates", + blank=True, + ), ), ] diff --git a/election/migrations/0002_election_archived.py b/election/migrations/0002_election_archived.py index fda74ef0..c113cf46 100644 --- a/election/migrations/0002_election_archived.py +++ b/election/migrations/0002_election_archived.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('election', '0001_initial'), - ] + dependencies = [("election", "0001_initial")] operations = [ migrations.AddField( - model_name='election', - name='archived', - field=models.BooleanField(verbose_name='archived', default=False), - ), + model_name="election", + name="archived", + field=models.BooleanField(verbose_name="archived", default=False), + ) ] diff --git a/election/migrations/0003_auto_20171202_1819.py b/election/migrations/0003_auto_20171202_1819.py index ef7868d6..e918bc06 100644 --- a/election/migrations/0003_auto_20171202_1819.py +++ b/election/migrations/0003_auto_20171202_1819.py @@ -6,18 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('election', '0002_election_archived'), - ] + dependencies = [("election", "0002_election_archived")] operations = [ - migrations.AlterModelOptions( - name='role', - options={'ordering': ('order',)}, - ), + migrations.AlterModelOptions(name="role", options={"ordering": ("order",)}), migrations.AddField( - model_name='role', - name='order', + model_name="role", + name="order", field=models.PositiveIntegerField(editable=False, default=0, db_index=True), preserve_default=False, ), diff --git a/election/models.py b/election/models.py index 6d31ed0a..5211aad6 100644 --- a/election/models.py +++ b/election/models.py @@ -10,30 +10,45 @@ class Election(models.Model): """ This class allows to create a new election """ - title = models.CharField(_('title'), max_length=255) - description = models.TextField(_('description'), null=True, blank=True) - start_candidature = models.DateTimeField(_('start candidature'), blank=False) - end_candidature = models.DateTimeField(_('end candidature'), blank=False) - start_date = models.DateTimeField(_('start date'), blank=False) - end_date = models.DateTimeField(_('end date'), blank=False) + + title = models.CharField(_("title"), max_length=255) + description = models.TextField(_("description"), null=True, blank=True) + start_candidature = models.DateTimeField(_("start candidature"), blank=False) + end_candidature = models.DateTimeField(_("end candidature"), blank=False) + start_date = models.DateTimeField(_("start date"), blank=False) + end_date = models.DateTimeField(_("end date"), blank=False) edit_groups = models.ManyToManyField( - Group, related_name="editable_elections", - verbose_name=_("edit groups"), blank=True) + Group, + related_name="editable_elections", + verbose_name=_("edit groups"), + blank=True, + ) view_groups = models.ManyToManyField( - Group, related_name="viewable_elections", - verbose_name=_("view groups"), blank=True) + Group, + related_name="viewable_elections", + verbose_name=_("view groups"), + blank=True, + ) vote_groups = models.ManyToManyField( - Group, related_name="votable_elections", - verbose_name=_("vote groups"), blank=True) + Group, + related_name="votable_elections", + verbose_name=_("vote groups"), + blank=True, + ) candidature_groups = models.ManyToManyField( - Group, related_name="candidate_elections", - verbose_name=_("candidature groups"), blank=True) + Group, + related_name="candidate_elections", + verbose_name=_("candidature groups"), + blank=True, + ) - voters = models.ManyToManyField(User, verbose_name=('voters'), related_name='voted_elections') + voters = models.ManyToManyField( + User, verbose_name=("voters"), related_name="voted_elections" + ) archived = models.BooleanField(_("archived"), default=False) def __str__(self): @@ -94,10 +109,13 @@ class Role(OrderedModel): """ This class allows to create a new role avaliable for a candidature """ - election = models.ForeignKey(Election, related_name='roles', verbose_name=_("election")) - title = models.CharField(_('title'), max_length=255) - description = models.TextField(_('description'), null=True, blank=True) - max_choice = models.IntegerField(_('max choice'), default=1) + + election = models.ForeignKey( + Election, related_name="roles", verbose_name=_("election") + ) + title = models.CharField(_("title"), max_length=255) + description = models.TextField(_("description"), null=True, blank=True) + max_choice = models.IntegerField(_("max choice"), default=1) def results(self, total_vote): results = {} @@ -105,18 +123,21 @@ class Role(OrderedModel): non_blank = 0 for candidature in self.candidatures.all(): cand_results = {} - cand_results['vote'] = self.votes.filter(candidature=candidature).count() + cand_results["vote"] = self.votes.filter(candidature=candidature).count() if total_vote == 0: - cand_results['percent'] = 0 + cand_results["percent"] = 0 else: - cand_results['percent'] = cand_results['vote'] * 100 / total_vote - non_blank += cand_results['vote'] + cand_results["percent"] = cand_results["vote"] * 100 / total_vote + non_blank += cand_results["vote"] results[candidature.user.username] = cand_results - results['total vote'] = total_vote + results["total vote"] = total_vote if total_vote == 0: - results['blank vote'] = {'vote': 0, 'percent': 0} + results["blank vote"] = {"vote": 0, "percent": 0} else: - results['blank vote'] = {'vote': total_vote - non_blank, 'percent': (total_vote - non_blank) * 100 / total_vote} + results["blank vote"] = { + "vote": total_vote - non_blank, + "percent": (total_vote - non_blank) * 100 / total_vote, + } return results @property @@ -131,8 +152,11 @@ class ElectionList(models.Model): """ To allow per list vote """ - title = models.CharField(_('title'), max_length=255) - election = models.ForeignKey(Election, related_name='election_lists', verbose_name=_("election")) + + title = models.CharField(_("title"), max_length=255) + election = models.ForeignKey( + Election, related_name="election_lists", verbose_name=_("election") + ) def can_be_edited_by(self, user): return user.can_edit(self.election) @@ -150,10 +174,15 @@ class Candidature(models.Model): """ This class is a component of responsability """ - role = models.ForeignKey(Role, related_name='candidatures', verbose_name=_("role")) - user = models.ForeignKey(User, verbose_name=_('user'), related_name='candidates', blank=True) - program = models.TextField(_('description'), null=True, blank=True) - election_list = models.ForeignKey(ElectionList, related_name='candidatures', verbose_name=_('election list')) + + role = models.ForeignKey(Role, related_name="candidatures", verbose_name=_("role")) + user = models.ForeignKey( + User, verbose_name=_("user"), related_name="candidates", blank=True + ) + program = models.TextField(_("description"), null=True, blank=True) + election_list = models.ForeignKey( + ElectionList, related_name="candidatures", verbose_name=_("election list") + ) def delete(self): for vote in self.votes.all(): @@ -171,8 +200,11 @@ class Vote(models.Model): """ This class allows to vote for candidates """ - role = models.ForeignKey(Role, related_name='votes', verbose_name=_("role")) - candidature = models.ManyToManyField(Candidature, related_name='votes', verbose_name=_("candidature")) + + role = models.ForeignKey(Role, related_name="votes", verbose_name=_("role")) + candidature = models.ManyToManyField( + Candidature, related_name="votes", verbose_name=_("candidature") + ) def __str__(self): return "Vote" diff --git a/election/tests.py b/election/tests.py index 61ad1226..1675d224 100644 --- a/election/tests.py +++ b/election/tests.py @@ -13,13 +13,11 @@ class MainElection(TestCase): self.election = Election.objects.all().first() self.public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID) - self.subscriber_group = Group.objects.get( - name=settings.SITH_MAIN_MEMBERS_GROUP) - self.ae_board_group = Group.objects.get( - name=settings.SITH_MAIN_BOARD_GROUP) - self.sli = User.objects.get(username='sli') - self.subscriber = User.objects.get(username='subscriber') - self.public = User.objects.get(username='public') + self.subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) + self.ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) + self.sli = User.objects.get(username="sli") + self.subscriber = User.objects.get(username="subscriber") + self.public = User.objects.get(username="public") class ElectionDetailTest(MainElection): @@ -27,11 +25,13 @@ class ElectionDetailTest(MainElection): self.election.view_groups.remove(self.public_group) self.election.view_groups.add(self.subscriber_group) self.election.save() - self.client.login(username=self.public.username, password='plop') - response_get = self.client.get(reverse('election:detail', - args=str(self.election.id))) - response_post = self.client.get(reverse('election:detail', - args=str(self.election.id))) + self.client.login(username=self.public.username, password="plop") + response_get = self.client.get( + reverse("election:detail", args=str(self.election.id)) + ) + response_post = self.client.get( + reverse("election:detail", args=str(self.election.id)) + ) self.assertTrue(response_get.status_code == 403) self.assertTrue(response_post.status_code == 403) self.election.view_groups.remove(self.subscriber_group) @@ -39,22 +39,26 @@ class ElectionDetailTest(MainElection): self.election.save() def test_permisson_granted(self): - self.client.login(username=self.public.username, password='plop') - response_get = self.client.get(reverse('election:detail', - args=str(self.election.id))) - response_post = self.client.post(reverse('election:detail', - args=str(self.election.id))) + self.client.login(username=self.public.username, password="plop") + response_get = self.client.get( + reverse("election:detail", args=str(self.election.id)) + ) + response_post = self.client.post( + reverse("election:detail", args=str(self.election.id)) + ) self.assertFalse(response_get.status_code == 403) self.assertFalse(response_post.status_code == 403) - self.assertTrue('La roue tourne' in str(response_get.content)) + self.assertTrue("La roue tourne" in str(response_get.content)) class ElectionUpdateView(MainElection): def test_permission_denied(self): - self.client.login(username=self.subscriber.username, password='plop') - response_get = self.client.get(reverse('election:update', - args=str(self.election.id))) - response_post = self.client.post(reverse('election:update', - args=str(self.election.id))) + self.client.login(username=self.subscriber.username, password="plop") + response_get = self.client.get( + reverse("election:update", args=str(self.election.id)) + ) + response_post = self.client.post( + reverse("election:update", args=str(self.election.id)) + ) self.assertTrue(response_get.status_code == 403) self.assertTrue(response_post.status_code == 403) diff --git a/election/urls.py b/election/urls.py index 2962d1ee..c7bde487 100644 --- a/election/urls.py +++ b/election/urls.py @@ -3,19 +3,53 @@ from django.conf.urls import url from election.views import * urlpatterns = [ - url(r'^$', ElectionsListView.as_view(), name='list'), - url(r'^archived$', ElectionListArchivedView.as_view(), name='list_archived'), - url(r'^add$', ElectionCreateView.as_view(), name='create'), - url(r'^(?P[0-9]+)/edit$', ElectionUpdateView.as_view(), name='update'), - url(r'^(?P[0-9]+)/delete$', ElectionDeleteView.as_view(), name='delete'), - url(r'^(?P[0-9]+)/list/add$', ElectionListCreateView.as_view(), name='create_list'), - url(r'^(?P[0-9]+)/list/delete$', ElectionListDeleteView.as_view(), name='delete_list'), - url(r'^(?P[0-9]+)/role/create$', RoleCreateView.as_view(), name='create_role'), - url(r'^(?P[0-9]+)/role/edit$', RoleUpdateView.as_view(), name='update_role'), - url(r'^(?P[0-9]+)/role/delete$', RoleDeleteView.as_view(), name='delete_role'), - url(r'^(?P[0-9]+)/candidate/add$', CandidatureCreateView.as_view(), name='candidate'), - url(r'^(?P[0-9]+)/candidate/edit$', CandidatureUpdateView.as_view(), name='update_candidate'), - url(r'^(?P[0-9]+)/candidate/delete$', CandidatureDeleteView.as_view(), name='delete_candidate'), - url(r'^(?P[0-9]+)/vote$', VoteFormView.as_view(), name='vote'), - url(r'^(?P[0-9]+)/detail$', ElectionDetailView.as_view(), name='detail'), + url(r"^$", ElectionsListView.as_view(), name="list"), + url(r"^archived$", ElectionListArchivedView.as_view(), name="list_archived"), + url(r"^add$", ElectionCreateView.as_view(), name="create"), + url(r"^(?P[0-9]+)/edit$", ElectionUpdateView.as_view(), name="update"), + url( + r"^(?P[0-9]+)/delete$", ElectionDeleteView.as_view(), name="delete" + ), + url( + r"^(?P[0-9]+)/list/add$", + ElectionListCreateView.as_view(), + name="create_list", + ), + url( + r"^(?P[0-9]+)/list/delete$", + ElectionListDeleteView.as_view(), + name="delete_list", + ), + url( + r"^(?P[0-9]+)/role/create$", + RoleCreateView.as_view(), + name="create_role", + ), + url( + r"^(?P[0-9]+)/role/edit$", RoleUpdateView.as_view(), name="update_role" + ), + url( + r"^(?P[0-9]+)/role/delete$", + RoleDeleteView.as_view(), + name="delete_role", + ), + url( + r"^(?P[0-9]+)/candidate/add$", + CandidatureCreateView.as_view(), + name="candidate", + ), + url( + r"^(?P[0-9]+)/candidate/edit$", + CandidatureUpdateView.as_view(), + name="update_candidate", + ), + url( + r"^(?P[0-9]+)/candidate/delete$", + CandidatureDeleteView.as_view(), + name="delete_candidate", + ), + url(r"^(?P[0-9]+)/vote$", VoteFormView.as_view(), name="vote"), + url( + r"^(?P[0-9]+)/detail$", ElectionDetailView.as_view(), name="detail" + ), ] diff --git a/election/views.py b/election/views.py index b562a1cb..77d00ac3 100644 --- a/election/views.py +++ b/election/views.py @@ -21,19 +21,30 @@ from ajax_select import make_ajax_field # Custom form field + class LimitedCheckboxField(forms.ModelMultipleChoiceField): """ Used to replace ModelMultipleChoiceField but with automatic backend verification """ - def __init__(self, queryset, max_choice, required=True, widget=None, - label=None, initial=None, help_text='', *args, **kwargs): + def __init__( + self, + queryset, + max_choice, + required=True, + widget=None, + label=None, + initial=None, + help_text="", + *args, + **kwargs + ): self.max_choice = max_choice widget = forms.CheckboxSelectMultiple() - super(LimitedCheckboxField, - self).__init__(queryset, None, required, widget, - label, initial, help_text, *args, **kwargs) + super(LimitedCheckboxField, self).__init__( + queryset, None, required, widget, label, initial, help_text, *args, **kwargs + ) def clean(self, value): qs = super(LimitedCheckboxField, self).clean(value) @@ -42,7 +53,9 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField): def validate(self, qs): if qs.count() > self.max_choice: - raise forms.ValidationError(_("You have selected too much candidates."), code='invalid') + raise forms.ValidationError( + _("You have selected too much candidates."), code="invalid" + ) # Forms @@ -50,24 +63,29 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField): class CandidateForm(forms.ModelForm): """ Form to candidate """ + class Meta: model = Candidature - fields = ['user', 'role', 'program', 'election_list'] - widgets = { - 'program': forms.Textarea - } + fields = ["user", "role", "program", "election_list"] + widgets = {"program": forms.Textarea} - user = AutoCompleteSelectField('users', label=_('User to candidate'), help_text=None, required=True) + user = AutoCompleteSelectField( + "users", label=_("User to candidate"), help_text=None, required=True + ) def __init__(self, *args, **kwargs): - election_id = kwargs.pop('election_id', None) - can_edit = kwargs.pop('can_edit', False) + election_id = kwargs.pop("election_id", None) + can_edit = kwargs.pop("can_edit", False) super(CandidateForm, self).__init__(*args, **kwargs) if election_id: - self.fields['role'].queryset = Role.objects.filter(election__id=election_id).all() - self.fields['election_list'].queryset = ElectionList.objects.filter(election__id=election_id).all() + self.fields["role"].queryset = Role.objects.filter( + election__id=election_id + ).all() + self.fields["election_list"].queryset = ElectionList.objects.filter( + election__id=election_id + ).all() if not can_edit: - self.fields['user'].widget = forms.HiddenInput() + self.fields["user"].widget = forms.HiddenInput() class VoteForm(forms.Form): @@ -77,67 +95,112 @@ class VoteForm(forms.Form): for role in election.roles.all(): cand = role.candidatures if role.max_choice > 1: - self.fields[role.title] = LimitedCheckboxField(cand, role.max_choice, required=False) + self.fields[role.title] = LimitedCheckboxField( + cand, role.max_choice, required=False + ) else: - self.fields[role.title] = forms.ModelChoiceField(cand, required=False, - widget=forms.RadioSelect(), - empty_label=_("Blank vote")) + self.fields[role.title] = forms.ModelChoiceField( + cand, + required=False, + widget=forms.RadioSelect(), + empty_label=_("Blank vote"), + ) class RoleForm(forms.ModelForm): """ Form for creating a role """ + class Meta: model = Role - fields = ['title', 'election', 'description', 'max_choice'] + fields = ["title", "election", "description", "max_choice"] def __init__(self, *args, **kwargs): - election_id = kwargs.pop('election_id', None) + election_id = kwargs.pop("election_id", None) super(RoleForm, self).__init__(*args, **kwargs) if election_id: - self.fields['election'].queryset = Election.objects.filter(id=election_id).all() + self.fields["election"].queryset = Election.objects.filter( + id=election_id + ).all() def clean(self): cleaned_data = super(RoleForm, self).clean() - title = cleaned_data.get('title') - election = cleaned_data.get('election') + title = cleaned_data.get("title") + election = cleaned_data.get("election") if Role.objects.filter(title=title, election=election).exists(): - raise forms.ValidationError(_("This role already exists for this election"), code='invalid') + raise forms.ValidationError( + _("This role already exists for this election"), code="invalid" + ) class ElectionListForm(forms.ModelForm): class Meta: model = ElectionList - fields = ('title', 'election') + fields = ("title", "election") def __init__(self, *args, **kwargs): - election_id = kwargs.pop('election_id', None) + election_id = kwargs.pop("election_id", None) super(ElectionListForm, self).__init__(*args, **kwargs) if election_id: - self.fields['election'].queryset = Election.objects.filter(id=election_id).all() + self.fields["election"].queryset = Election.objects.filter( + id=election_id + ).all() class ElectionForm(forms.ModelForm): class Meta: model = Election - fields = ['title', 'description', 'archived', - 'start_candidature', 'end_candidature', - 'start_date', 'end_date', - 'edit_groups', 'view_groups', - 'vote_groups', 'candidature_groups'] + fields = [ + "title", + "description", + "archived", + "start_candidature", + "end_candidature", + "start_date", + "end_date", + "edit_groups", + "view_groups", + "vote_groups", + "candidature_groups", + ] - edit_groups = make_ajax_field(Election, 'edit_groups', 'groups', help_text="", label=_("edit groups")) - view_groups = make_ajax_field(Election, 'view_groups', 'groups', help_text="", label=_("view groups")) - vote_groups = make_ajax_field(Election, 'vote_groups', 'groups', help_text="", label=_("vote groups")) - candidature_groups = make_ajax_field(Election, 'candidature_groups', 'groups', help_text="", label=_("candidature groups")) + edit_groups = make_ajax_field( + Election, "edit_groups", "groups", help_text="", label=_("edit groups") + ) + view_groups = make_ajax_field( + Election, "view_groups", "groups", help_text="", label=_("view groups") + ) + vote_groups = make_ajax_field( + Election, "vote_groups", "groups", help_text="", label=_("vote groups") + ) + candidature_groups = make_ajax_field( + Election, + "candidature_groups", + "groups", + help_text="", + label=_("candidature groups"), + ) - start_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), - widget=SelectDateTime, required=True) - end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), - widget=SelectDateTime, required=True) - start_candidature = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start candidature"), - widget=SelectDateTime, required=True) - end_candidature = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End candidature"), - widget=SelectDateTime, required=True) + start_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Start date"), + widget=SelectDateTime, + required=True, + ) + end_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], label=_("End date"), widget=SelectDateTime, required=True + ) + start_candidature = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Start candidature"), + widget=SelectDateTime, + required=True, + ) + end_candidature = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End candidature"), + widget=SelectDateTime, + required=True, + ) # Display elections @@ -147,42 +210,52 @@ class ElectionsListView(CanViewMixin, ListView): """ A list of all non archived elections visible """ + model = Election ordering = ["-id"] paginate_by = 10 - template_name = 'election/election_list.jinja' + template_name = "election/election_list.jinja" def get_queryset(self): - return super(ElectionsListView, self).get_queryset().filter(archived=False).all() + return ( + super(ElectionsListView, self).get_queryset().filter(archived=False).all() + ) class ElectionListArchivedView(CanViewMixin, ListView): """ A list of all archived elections visible """ + model = Election ordering = ["-id"] paginate_by = 10 - template_name = 'election/election_list.jinja' + template_name = "election/election_list.jinja" def get_queryset(self): - return super(ElectionListArchivedView, self).get_queryset().filter(archived=True).all() + return ( + super(ElectionListArchivedView, self) + .get_queryset() + .filter(archived=True) + .all() + ) class ElectionDetailView(CanViewMixin, DetailView): """ Details an election responsability by responsability """ + model = Election - template_name = 'election/election_detail.jinja' + template_name = "election/election_detail.jinja" pk_url_kwarg = "election_id" def get(self, request, *arg, **kwargs): r = super(ElectionDetailView, self).get(request, *arg, **kwargs) election = self.get_object() if request.user.can_edit(election) and election.is_vote_editable: - action = request.GET.get('action', None) - role = request.GET.get('role', None) + action = request.GET.get("action", None) + role = request.GET.get("role", None) if action and role and Role.objects.filter(id=role).exists(): if action == "up": Role.objects.get(id=role).up() @@ -192,28 +265,32 @@ class ElectionDetailView(CanViewMixin, DetailView): Role.objects.get(id=role).bottom() elif action == "top": Role.objects.get(id=role).top() - return redirect(reverse_lazy('election:detail', kwargs={'election_id': election.id})) + return redirect( + reverse_lazy("election:detail", kwargs={"election_id": election.id}) + ) return r def get_context_data(self, **kwargs): """ Add additionnal data to the template """ kwargs = super(ElectionDetailView, self).get_context_data(**kwargs) - kwargs['election_form'] = VoteForm(self.object, self.request.user) - kwargs['election_results'] = self.object.results + kwargs["election_form"] = VoteForm(self.object, self.request.user) + kwargs["election_results"] = self.object.results return kwargs # Form view + class VoteFormView(CanCreateMixin, FormView): """ Alows users to vote """ + form_class = VoteForm - template_name = 'election/election_detail.jinja' + template_name = "election/election_detail.jinja" def dispatch(self, request, *arg, **kwargs): - self.election = get_object_or_404(Election, pk=kwargs['election_id']) + self.election = get_object_or_404(Election, pk=kwargs["election_id"]) return super(VoteFormView, self).dispatch(request, *arg, **kwargs) def vote(self, election_data): @@ -235,8 +312,8 @@ class VoteFormView(CanCreateMixin, FormView): def get_form_kwargs(self): kwargs = super(VoteFormView, self).get_form_kwargs() - kwargs['election'] = self.election - kwargs['user'] = self.request.user + kwargs["election"] = self.election + kwargs["user"] = self.request.user return kwargs def form_valid(self, form): @@ -252,41 +329,43 @@ class VoteFormView(CanCreateMixin, FormView): return res def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) def get_context_data(self, **kwargs): """ Add additionnal data to the template """ kwargs = super(VoteFormView, self).get_context_data(**kwargs) - kwargs['object'] = self.election - kwargs['election'] = self.election - kwargs['election_form'] = self.get_form() + kwargs["object"] = self.election + kwargs["election"] = self.election + kwargs["election_form"] = self.get_form() return kwargs # Create views + class CandidatureCreateView(CanCreateMixin, CreateView): """ View dedicated to a cundidature creation """ + form_class = CandidateForm model = Candidature - template_name = 'election/candidate_form.jinja' + template_name = "election/candidate_form.jinja" def dispatch(self, request, *arg, **kwargs): - self.election = get_object_or_404(Election, pk=kwargs['election_id']) + self.election = get_object_or_404(Election, pk=kwargs["election_id"]) return super(CandidatureCreateView, self).dispatch(request, *arg, **kwargs) def get_initial(self): init = {} self.can_edit = self.request.user.can_edit(self.election) - init['user'] = self.request.user.id + init["user"] = self.request.user.id return init def get_form_kwargs(self): kwargs = super(CandidatureCreateView, self).get_form_kwargs() - kwargs['election_id'] = self.election.id - kwargs['can_edit'] = self.can_edit + kwargs["election_id"] = self.election.id + kwargs["can_edit"] = self.can_edit return kwargs def form_valid(self, form): @@ -295,23 +374,25 @@ class CandidatureCreateView(CanCreateMixin, CreateView): """ obj = form.instance obj.election = Election.objects.get(id=self.election.id) - if(obj.election.can_candidate(obj.user)) and (obj.user == self.request.user or self.can_edit): + if (obj.election.can_candidate(obj.user)) and ( + obj.user == self.request.user or self.can_edit + ): return super(CreateView, self).form_valid(form) raise PermissionDenied def get_context_data(self, **kwargs): kwargs = super(CandidatureCreateView, self).get_context_data(**kwargs) - kwargs['election'] = self.election + kwargs["election"] = self.election return kwargs def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) class ElectionCreateView(CanCreateMixin, CreateView): model = Election form_class = ElectionForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def dispatch(self, request, *args, **kwargs): if not request.user.is_subscribed: @@ -326,23 +407,23 @@ class ElectionCreateView(CanCreateMixin, CreateView): return super(CreateView, self).form_valid(form) def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.object.id}) class RoleCreateView(CanCreateMixin, CreateView): model = Role form_class = RoleForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def dispatch(self, request, *arg, **kwargs): - self.election = get_object_or_404(Election, pk=kwargs['election_id']) + self.election = get_object_or_404(Election, pk=kwargs["election_id"]) if not self.election.is_vote_editable: raise PermissionDenied return super(RoleCreateView, self).dispatch(request, *arg, **kwargs) def get_initial(self): init = {} - init['election'] = self.election + init["election"] = self.election return init def form_valid(self, form): @@ -358,32 +439,34 @@ class RoleCreateView(CanCreateMixin, CreateView): def get_form_kwargs(self): kwargs = super(RoleCreateView, self).get_form_kwargs() - kwargs['election_id'] = self.election.id + kwargs["election_id"] = self.election.id return kwargs def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id}) + return reverse_lazy( + "election:detail", kwargs={"election_id": self.object.election.id} + ) class ElectionListCreateView(CanCreateMixin, CreateView): model = ElectionList form_class = ElectionListForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def dispatch(self, request, *arg, **kwargs): - self.election = get_object_or_404(Election, pk=kwargs['election_id']) + self.election = get_object_or_404(Election, pk=kwargs["election_id"]) if not self.election.is_vote_editable: raise PermissionDenied return super(ElectionListCreateView, self).dispatch(request, *arg, **kwargs) def get_initial(self): init = {} - init['election'] = self.election + init["election"] = self.election return init def get_form_kwargs(self): kwargs = super(ElectionListCreateView, self).get_form_kwargs() - kwargs['election_id'] = self.election.id + kwargs["election_id"] = self.election.id return kwargs def form_valid(self, form): @@ -401,7 +484,10 @@ class ElectionListCreateView(CanCreateMixin, CreateView): raise PermissionDenied def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id}) + return reverse_lazy( + "election:detail", kwargs={"election_id": self.object.election.id} + ) + # Update view @@ -409,38 +495,42 @@ class ElectionListCreateView(CanCreateMixin, CreateView): class ElectionUpdateView(CanEditMixin, UpdateView): model = Election form_class = ElectionForm - template_name = 'core/edit.jinja' - pk_url_kwarg = 'election_id' + template_name = "core/edit.jinja" + pk_url_kwarg = "election_id" def get_initial(self): init = {} try: - init['start_date'] = self.object.start_date.strftime('%Y-%m-%d %H:%M:%S') + init["start_date"] = self.object.start_date.strftime("%Y-%m-%d %H:%M:%S") except Exception: pass try: - init['end_date'] = self.object.end_date.strftime('%Y-%m-%d %H:%M:%S') + init["end_date"] = self.object.end_date.strftime("%Y-%m-%d %H:%M:%S") except Exception: pass try: - init['start_candidature'] = self.object.start_candidature.strftime('%Y-%m-%d %H:%M:%S') + init["start_candidature"] = self.object.start_candidature.strftime( + "%Y-%m-%d %H:%M:%S" + ) except Exception: pass try: - init['end_candidature'] = self.object.end_candidature.strftime('%Y-%m-%d %H:%M:%S') + init["end_candidature"] = self.object.end_candidature.strftime( + "%Y-%m-%d %H:%M:%S" + ) except Exception: pass return init def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.object.id}) class CandidatureUpdateView(CanEditMixin, UpdateView): model = Candidature form_class = CandidateForm - template_name = 'core/edit.jinja' - pk_url_kwarg = 'candidature_id' + template_name = "core/edit.jinja" + pk_url_kwarg = "candidature_id" def dispatch(self, request, *arg, **kwargs): self.object = self.get_object() @@ -449,7 +539,7 @@ class CandidatureUpdateView(CanEditMixin, UpdateView): return super(CandidatureUpdateView, self).dispatch(request, *arg, **kwargs) def remove_fields(self): - self.form.fields.pop('role', None) + self.form.fields.pop("role", None) def get(self, request, *args, **kwargs): self.form = self.get_form() @@ -459,24 +549,30 @@ class CandidatureUpdateView(CanEditMixin, UpdateView): def post(self, request, *args, **kwargs): self.form = self.get_form() self.remove_fields() - if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): + if ( + request.user.is_authenticated() + and request.user.can_edit(self.object) + and self.form.is_valid() + ): return super(CandidatureUpdateView, self).form_valid(self.form) return self.form_invalid(self.form) def get_form_kwargs(self): kwargs = super(CandidatureUpdateView, self).get_form_kwargs() - kwargs['election_id'] = self.object.role.election.id + kwargs["election_id"] = self.object.role.election.id return kwargs def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.role.election.id}) + return reverse_lazy( + "election:detail", kwargs={"election_id": self.object.role.election.id} + ) class RoleUpdateView(CanEditMixin, UpdateView): model = Role form_class = RoleForm - template_name = 'core/edit.jinja' - pk_url_kwarg = 'role_id' + template_name = "core/edit.jinja" + pk_url_kwarg = "role_id" def dispatch(self, request, *arg, **kwargs): self.object = self.get_object() @@ -485,7 +581,7 @@ class RoleUpdateView(CanEditMixin, UpdateView): return super(RoleUpdateView, self).dispatch(request, *arg, **kwargs) def remove_fields(self): - self.form.fields.pop('election', None) + self.form.fields.pop("election", None) def get(self, request, *args, **kwargs): self.object = self.get_object() @@ -497,25 +593,32 @@ class RoleUpdateView(CanEditMixin, UpdateView): self.object = self.get_object() self.form = self.get_form() self.remove_fields() - if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): + if ( + request.user.is_authenticated() + and request.user.can_edit(self.object) + and self.form.is_valid() + ): return super(RoleUpdateView, self).form_valid(self.form) return self.form_invalid(self.form) def get_form_kwargs(self): kwargs = super(RoleUpdateView, self).get_form_kwargs() - kwargs['election_id'] = self.object.election.id + kwargs["election_id"] = self.object.election.id return kwargs def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id}) + return reverse_lazy( + "election:detail", kwargs={"election_id": self.object.election.id} + ) + # Delete Views class ElectionDeleteView(DeleteView): model = Election - template_name = 'core/delete_confirm.jinja' - pk_url_kwarg = 'election_id' + template_name = "core/delete_confirm.jinja" + pk_url_kwarg = "election_id" def dispatch(self, request, *args, **kwargs): if request.user.is_root: @@ -523,13 +626,13 @@ class ElectionDeleteView(DeleteView): raise PermissionDenied def get_success_url(self, **kwargs): - return reverse_lazy('election:list') + return reverse_lazy("election:list") class CandidatureDeleteView(CanEditMixin, DeleteView): model = Candidature - template_name = 'core/delete_confirm.jinja' - pk_url_kwarg = 'candidature_id' + template_name = "core/delete_confirm.jinja" + pk_url_kwarg = "candidature_id" def dispatch(self, request, *arg, **kwargs): self.object = self.get_object() @@ -539,13 +642,13 @@ class CandidatureDeleteView(CanEditMixin, DeleteView): return super(CandidatureDeleteView, self).dispatch(request, *arg, **kwargs) def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) class RoleDeleteView(CanEditMixin, DeleteView): model = Role - template_name = 'core/delete_confirm.jinja' - pk_url_kwarg = 'role_id' + template_name = "core/delete_confirm.jinja" + pk_url_kwarg = "role_id" def dispatch(self, request, *arg, **kwargs): self.object = self.get_object() @@ -555,13 +658,13 @@ class RoleDeleteView(CanEditMixin, DeleteView): return super(RoleDeleteView, self).dispatch(request, *arg, **kwargs) def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) class ElectionListDeleteView(CanEditMixin, DeleteView): model = ElectionList - template_name = 'core/delete_confirm.jinja' - pk_url_kwarg = 'list_id' + template_name = "core/delete_confirm.jinja" + pk_url_kwarg = "list_id" def dispatch(self, request, *args, **kwargs): self.object = self.get_object() @@ -571,4 +674,4 @@ class ElectionListDeleteView(CanEditMixin, DeleteView): return super(ElectionListDeleteView, self).dispatch(request, *args, **kwargs) def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) diff --git a/forum/__init__.py b/forum/__init__.py index 0a9419f8..0ace29c4 100644 --- a/forum/__init__.py +++ b/forum/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/forum/admin.py b/forum/admin.py index 99462dfe..330243e8 100644 --- a/forum/admin.py +++ b/forum/admin.py @@ -45,4 +45,3 @@ admin.site.register(Forum, ForumAdmin) admin.site.register(ForumTopic, ForumTopicAdmin) admin.site.register(ForumMessage, ForumMessageAdmin) admin.site.register(ForumUserInfo) - diff --git a/forum/migrations/0001_initial.py b/forum/migrations/0001_initial.py index 5e5a499c..63aa4dd8 100644 --- a/forum/migrations/0001_initial.py +++ b/forum/migrations/0001_initial.py @@ -12,71 +12,211 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('club', '0006_auto_20161229_0040'), - ('core', '0019_preferences_receive_weekmail'), + ("club", "0006_auto_20161229_0040"), + ("core", "0019_preferences_receive_weekmail"), ] operations = [ migrations.CreateModel( - name='Forum', + name="Forum", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('description', models.CharField(max_length=256, verbose_name='description', default='')), - ('is_category', models.BooleanField(verbose_name='is a category', default=False)), - ('edit_groups', models.ManyToManyField(related_name='editable_forums', to='core.Group', blank=True, default=[4])), - ('owner_club', models.ForeignKey(to='club.Club', verbose_name='owner club', related_name='owned_forums', default=1)), - ('parent', models.ForeignKey(to='forum.Forum', null=True, related_name='children', blank=True)), - ('view_groups', models.ManyToManyField(related_name='viewable_forums', to='core.Group', blank=True, default=[2])), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "description", + models.CharField( + max_length=256, verbose_name="description", default="" + ), + ), + ( + "is_category", + models.BooleanField(verbose_name="is a category", default=False), + ), + ( + "edit_groups", + models.ManyToManyField( + related_name="editable_forums", + to="core.Group", + blank=True, + default=[4], + ), + ), + ( + "owner_club", + models.ForeignKey( + to="club.Club", + verbose_name="owner club", + related_name="owned_forums", + default=1, + ), + ), + ( + "parent", + models.ForeignKey( + to="forum.Forum", null=True, related_name="children", blank=True + ), + ), + ( + "view_groups", + models.ManyToManyField( + related_name="viewable_forums", + to="core.Group", + blank=True, + default=[2], + ), + ), ], ), migrations.CreateModel( - name='ForumMessage', + name="ForumMessage", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('title', models.CharField(max_length=64, blank=True, verbose_name='title', default='')), - ('message', models.TextField(verbose_name='message', default='')), - ('date', models.DateTimeField(verbose_name='date', default=django.utils.timezone.now)), - ('author', models.ForeignKey(related_name='forum_messages', to=settings.AUTH_USER_MODEL)), - ('readers', models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name='readers', related_name='read_messages')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "title", + models.CharField( + max_length=64, blank=True, verbose_name="title", default="" + ), + ), + ("message", models.TextField(verbose_name="message", default="")), + ( + "date", + models.DateTimeField( + verbose_name="date", default=django.utils.timezone.now + ), + ), + ( + "author", + models.ForeignKey( + related_name="forum_messages", to=settings.AUTH_USER_MODEL + ), + ), + ( + "readers", + models.ManyToManyField( + to=settings.AUTH_USER_MODEL, + verbose_name="readers", + related_name="read_messages", + ), + ), ], - options={ - 'ordering': ['id'], - }, + options={"ordering": ["id"]}, ), migrations.CreateModel( - name='ForumMessageMeta', + name="ForumMessageMeta", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('date', models.DateTimeField(verbose_name='date', default=django.utils.timezone.now)), - ('action', models.CharField(max_length=16, choices=[('EDIT', 'Message edited by'), ('DELETE', 'Message deleted by'), ('UNDELETE', 'Message undeleted by')], verbose_name='action')), - ('message', models.ForeignKey(related_name='metas', to='forum.ForumMessage')), - ('user', models.ForeignKey(related_name='forum_message_metas', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "date", + models.DateTimeField( + verbose_name="date", default=django.utils.timezone.now + ), + ), + ( + "action", + models.CharField( + max_length=16, + choices=[ + ("EDIT", "Message edited by"), + ("DELETE", "Message deleted by"), + ("UNDELETE", "Message undeleted by"), + ], + verbose_name="action", + ), + ), + ( + "message", + models.ForeignKey(related_name="metas", to="forum.ForumMessage"), + ), + ( + "user", + models.ForeignKey( + related_name="forum_message_metas", to=settings.AUTH_USER_MODEL + ), + ), ], ), migrations.CreateModel( - name='ForumTopic', + name="ForumTopic", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('description', models.CharField(max_length=256, verbose_name='description', default='')), - ('author', models.ForeignKey(related_name='forum_topics', to=settings.AUTH_USER_MODEL)), - ('forum', models.ForeignKey(related_name='topics', to='forum.Forum')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "description", + models.CharField( + max_length=256, verbose_name="description", default="" + ), + ), + ( + "author", + models.ForeignKey( + related_name="forum_topics", to=settings.AUTH_USER_MODEL + ), + ), + ("forum", models.ForeignKey(related_name="topics", to="forum.Forum")), ], - options={ - 'ordering': ['-id'], - }, + options={"ordering": ["-id"]}, ), migrations.CreateModel( - name='ForumUserInfo', + name="ForumUserInfo", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('last_read_date', models.DateTimeField(verbose_name='last read date', default=datetime.datetime(1999, 1, 1, 0, 0, tzinfo=utc))), - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='_forum_infos')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "last_read_date", + models.DateTimeField( + verbose_name="last read date", + default=datetime.datetime(1999, 1, 1, 0, 0, tzinfo=utc), + ), + ), + ( + "user", + models.OneToOneField( + to=settings.AUTH_USER_MODEL, related_name="_forum_infos" + ), + ), ], ), migrations.AddField( - model_name='forummessage', - name='topic', - field=models.ForeignKey(related_name='messages', to='forum.ForumTopic'), + model_name="forummessage", + name="topic", + field=models.ForeignKey(related_name="messages", to="forum.ForumTopic"), ), ] diff --git a/forum/migrations/0002_auto_20170312_1753.py b/forum/migrations/0002_auto_20170312_1753.py index fdd0a431..a0807c47 100644 --- a/forum/migrations/0002_auto_20170312_1753.py +++ b/forum/migrations/0002_auto_20170312_1753.py @@ -6,23 +6,25 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('forum', '0001_initial'), - ] + dependencies = [("forum", "0001_initial")] operations = [ - migrations.AlterModelOptions( - name='forum', - options={'ordering': ['number']}, - ), + migrations.AlterModelOptions(name="forum", options={"ordering": ["number"]}), migrations.AddField( - model_name='forum', - name='number', - field=models.IntegerField(verbose_name='number to choose a specific forum ordering', default=1), + model_name="forum", + name="number", + field=models.IntegerField( + verbose_name="number to choose a specific forum ordering", default=1 + ), ), migrations.AlterField( - model_name='forum', - name='edit_groups', - field=models.ManyToManyField(related_name='editable_forums', blank=True, to='core.Group', default=[331]), + model_name="forum", + name="edit_groups", + field=models.ManyToManyField( + related_name="editable_forums", + blank=True, + to="core.Group", + default=[331], + ), ), ] diff --git a/forum/migrations/0003_auto_20170510_1754.py b/forum/migrations/0003_auto_20170510_1754.py index 6b2c26f1..db175969 100644 --- a/forum/migrations/0003_auto_20170510_1754.py +++ b/forum/migrations/0003_auto_20170510_1754.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('forum', '0002_auto_20170312_1753'), - ] + dependencies = [("forum", "0002_auto_20170312_1753")] operations = [ migrations.AlterField( - model_name='forum', - name='edit_groups', - field=models.ManyToManyField(blank=True, default=[4], related_name='editable_forums', to='core.Group'), - ), + model_name="forum", + name="edit_groups", + field=models.ManyToManyField( + blank=True, default=[4], related_name="editable_forums", to="core.Group" + ), + ) ] diff --git a/forum/migrations/0004_auto_20170531_1949.py b/forum/migrations/0004_auto_20170531_1949.py index 0ae64560..d6c4a660 100644 --- a/forum/migrations/0004_auto_20170531_1949.py +++ b/forum/migrations/0004_auto_20170531_1949.py @@ -7,57 +7,67 @@ import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('forum', '0003_auto_20170510_1754'), - ] + dependencies = [("forum", "0003_auto_20170510_1754")] operations = [ migrations.AlterModelOptions( - name='forummessage', - options={'ordering': ['-date']}, + name="forummessage", options={"ordering": ["-date"]} ), migrations.AlterModelOptions( - name='forumtopic', - options={'ordering': ['-_last_message__date']}, + name="forumtopic", options={"ordering": ["-_last_message__date"]} ), migrations.AddField( - model_name='forum', - name='_last_message', - field=models.ForeignKey(verbose_name='the last message', to='forum.ForumMessage', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='forums_where_its_last'), + model_name="forum", + name="_last_message", + field=models.ForeignKey( + verbose_name="the last message", + to="forum.ForumMessage", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="forums_where_its_last", + ), ), migrations.AddField( - model_name='forum', - name='_topic_number', - field=models.IntegerField(default=0, verbose_name='number of topics'), + model_name="forum", + name="_topic_number", + field=models.IntegerField(default=0, verbose_name="number of topics"), ), migrations.AddField( - model_name='forummessage', - name='_deleted', - field=models.BooleanField(default=False, verbose_name='is deleted'), + model_name="forummessage", + name="_deleted", + field=models.BooleanField(default=False, verbose_name="is deleted"), ), migrations.AddField( - model_name='forumtopic', - name='_last_message', - field=models.ForeignKey(verbose_name='the last message', to='forum.ForumMessage', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+'), + model_name="forumtopic", + name="_last_message", + field=models.ForeignKey( + verbose_name="the last message", + to="forum.ForumMessage", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + ), ), migrations.AddField( - model_name='forumtopic', - name='_message_number', - field=models.IntegerField(default=0, verbose_name='number of messages'), + model_name="forumtopic", + name="_message_number", + field=models.IntegerField(default=0, verbose_name="number of messages"), ), migrations.AddField( - model_name='forumtopic', - name='_title', - field=models.CharField(max_length=64, blank=True, verbose_name='title'), + model_name="forumtopic", + name="_title", + field=models.CharField(max_length=64, blank=True, verbose_name="title"), ), migrations.AlterField( - model_name='forum', - name='description', - field=models.CharField(max_length=512, default='', verbose_name='description'), + model_name="forum", + name="description", + field=models.CharField( + max_length=512, default="", verbose_name="description" + ), ), migrations.AlterField( - model_name='forum', - name='id', + model_name="forum", + name="id", field=models.AutoField(primary_key=True, serialize=False, db_index=True), ), ] diff --git a/forum/migrations/0005_forumtopic_subscribed_users.py b/forum/migrations/0005_forumtopic_subscribed_users.py index f82f3836..9e6e83c7 100644 --- a/forum/migrations/0005_forumtopic_subscribed_users.py +++ b/forum/migrations/0005_forumtopic_subscribed_users.py @@ -9,13 +9,17 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('forum', '0004_auto_20170531_1949'), + ("forum", "0004_auto_20170531_1949"), ] operations = [ migrations.AddField( - model_name='forumtopic', - name='subscribed_users', - field=models.ManyToManyField(verbose_name='subscribed users', related_name='favorite_topics', to=settings.AUTH_USER_MODEL), - ), + model_name="forumtopic", + name="subscribed_users", + field=models.ManyToManyField( + verbose_name="subscribed users", + related_name="favorite_topics", + to=settings.AUTH_USER_MODEL, + ), + ) ] diff --git a/forum/migrations/0006_auto_20180426_2013.py b/forum/migrations/0006_auto_20180426_2013.py index 92c53d9f..08ceed56 100644 --- a/forum/migrations/0006_auto_20180426_2013.py +++ b/forum/migrations/0006_auto_20180426_2013.py @@ -7,19 +7,27 @@ import forum.models class Migration(migrations.Migration): - dependencies = [ - ('forum', '0005_forumtopic_subscribed_users'), - ] + dependencies = [("forum", "0005_forumtopic_subscribed_users")] operations = [ migrations.AlterField( - model_name='forum', - name='edit_groups', - field=models.ManyToManyField(blank=True, default=forum.models.Forum.get_default_edit_group, related_name='editable_forums', to='core.Group'), + model_name="forum", + name="edit_groups", + field=models.ManyToManyField( + blank=True, + default=forum.models.Forum.get_default_edit_group, + related_name="editable_forums", + to="core.Group", + ), ), migrations.AlterField( - model_name='forum', - name='view_groups', - field=models.ManyToManyField(blank=True, default=forum.models.Forum.get_default_view_group, related_name='viewable_forums', to='core.Group'), + model_name="forum", + name="view_groups", + field=models.ManyToManyField( + blank=True, + default=forum.models.Forum.get_default_view_group, + related_name="viewable_forums", + to="core.Group", + ), ), ] diff --git a/forum/models.py b/forum/models.py index 7f04779c..523b1169 100644 --- a/forum/models.py +++ b/forum/models.py @@ -38,7 +38,6 @@ from core.models import User, Group from club.models import Club - class Forum(models.Model): """ The Forum class, made as a tree to allow nice tidy organization @@ -47,27 +46,51 @@ class Forum(models.Model): edit_groups allows to put any group as a forum admin view_groups allows some groups to view a forum """ + # Those functions prevent generating migration upon settings changes - def get_default_edit_group(): return [settings.SITH_GROUP_OLD_SUBSCRIBERS_ID] - def get_default_view_group(): return [settings.SITH_GROUP_PUBLIC_ID] + def get_default_edit_group(): + return [settings.SITH_GROUP_OLD_SUBSCRIBERS_ID] + + def get_default_view_group(): + return [settings.SITH_GROUP_PUBLIC_ID] + id = models.AutoField(primary_key=True, db_index=True) - name = models.CharField(_('name'), max_length=64) - description = models.CharField(_('description'), max_length=512, default="") - is_category = models.BooleanField(_('is a category'), default=False) - parent = models.ForeignKey('Forum', related_name='children', null=True, blank=True) - owner_club = models.ForeignKey(Club, related_name="owned_forums", verbose_name=_("owner club"), - default=settings.SITH_MAIN_CLUB_ID) - edit_groups = models.ManyToManyField(Group, related_name="editable_forums", blank=True, - default=get_default_edit_group) - view_groups = models.ManyToManyField(Group, related_name="viewable_forums", blank=True, - default=get_default_view_group) - number = models.IntegerField(_("number to choose a specific forum ordering"), default=1) - _last_message = models.ForeignKey('ForumMessage', related_name="forums_where_its_last", - verbose_name=_("the last message"), null=True, on_delete=models.SET_NULL) + name = models.CharField(_("name"), max_length=64) + description = models.CharField(_("description"), max_length=512, default="") + is_category = models.BooleanField(_("is a category"), default=False) + parent = models.ForeignKey("Forum", related_name="children", null=True, blank=True) + owner_club = models.ForeignKey( + Club, + related_name="owned_forums", + verbose_name=_("owner club"), + default=settings.SITH_MAIN_CLUB_ID, + ) + edit_groups = models.ManyToManyField( + Group, + related_name="editable_forums", + blank=True, + default=get_default_edit_group, + ) + view_groups = models.ManyToManyField( + Group, + related_name="viewable_forums", + blank=True, + default=get_default_view_group, + ) + number = models.IntegerField( + _("number to choose a specific forum ordering"), default=1 + ) + _last_message = models.ForeignKey( + "ForumMessage", + related_name="forums_where_its_last", + verbose_name=_("the last message"), + null=True, + on_delete=models.SET_NULL, + ) _topic_number = models.IntegerField(_("number of topics"), default=0) class Meta: - ordering = ['number'] + ordering = ["number"] def clean(self): self.check_loop() @@ -87,8 +110,18 @@ class Forum(models.Model): self.parent.set_topic_number() def set_last_message(self): - topic = ForumTopic.objects.filter(forum__id=self.id).exclude(_last_message=None).order_by('-_last_message__id').first() - forum = Forum.objects.filter(parent__id=self.id).exclude(_last_message=None).order_by('-_last_message__id').first() + topic = ( + ForumTopic.objects.filter(forum__id=self.id) + .exclude(_last_message=None) + .order_by("-_last_message__id") + .first() + ) + forum = ( + Forum.objects.filter(parent__id=self.id) + .exclude(_last_message=None) + .order_by("-_last_message__id") + .first() + ) if topic and forum: if topic._last_message_id < forum._last_message_id: self._last_message_id = forum._last_message_id @@ -117,8 +150,8 @@ class Forum(models.Model): self.save() _club_memberships = {} # This cache is particularly efficient: - # divided by 3 the number of requests on the main forum page - # after the first load + # divided by 3 the number of requests on the main forum page + # after the first load def is_owned_by(self, user): if user.is_in_group(settings.SITH_GROUP_FORUM_ADMIN_ID): return True @@ -141,7 +174,7 @@ class Forum(models.Model): cur = self while cur.parent is not None: if cur in objs: - raise ValidationError(_('You can not make loops in forums')) + raise ValidationError(_("You can not make loops in forums")) objs.append(cur) cur = cur.parent @@ -149,10 +182,14 @@ class Forum(models.Model): return "%s" % (self.name) def get_full_name(self): - return '/'.join(chain.from_iterable([[parent.name for parent in self.get_parent_list()], [self.name]])) + return "/".join( + chain.from_iterable( + [[parent.name for parent in self.get_parent_list()], [self.name]] + ) + ) def get_absolute_url(self): - return reverse('forum:view_forum', kwargs={'forum_id': self.id}) + return reverse("forum:view_forum", kwargs={"forum_id": self.id}) @cached_property def parent_list(self): @@ -189,17 +226,24 @@ class Forum(models.Model): class ForumTopic(models.Model): - forum = models.ForeignKey(Forum, related_name='topics') - author = models.ForeignKey(User, related_name='forum_topics') - description = models.CharField(_('description'), max_length=256, default="") - subscribed_users = models.ManyToManyField(User, related_name='favorite_topics', verbose_name=_("subscribed users")) - _last_message = models.ForeignKey('ForumMessage', related_name="+", verbose_name=_("the last message"), - null=True, on_delete=models.SET_NULL) - _title = models.CharField(_('title'), max_length=64, blank=True) + forum = models.ForeignKey(Forum, related_name="topics") + author = models.ForeignKey(User, related_name="forum_topics") + description = models.CharField(_("description"), max_length=256, default="") + subscribed_users = models.ManyToManyField( + User, related_name="favorite_topics", verbose_name=_("subscribed users") + ) + _last_message = models.ForeignKey( + "ForumMessage", + related_name="+", + verbose_name=_("the last message"), + null=True, + on_delete=models.SET_NULL, + ) + _title = models.CharField(_("title"), max_length=64, blank=True) _message_number = models.IntegerField(_("number of messages"), default=0) class Meta: - ordering = ['-_last_message__date'] + ordering = ["-_last_message__date"] def save(self, *args, **kwargs): super(ForumTopic, self).save(*args, **kwargs) @@ -219,11 +263,16 @@ class ForumTopic(models.Model): return "%s" % (self.title) def get_absolute_url(self): - return reverse('forum:view_topic', kwargs={'topic_id': self.id}) + return reverse("forum:view_topic", kwargs={"topic_id": self.id}) def get_first_unread_message(self, user): try: - msg = self.messages.exclude(readers=user).filter(date__gte=user.forum_infos.last_read_date).order_by('id').first() + msg = ( + self.messages.exclude(readers=user) + .filter(date__gte=user.forum_infos.last_read_date) + .order_by("id") + .first() + ) return msg except: return None @@ -241,16 +290,19 @@ class ForumMessage(models.Model): """ "A ForumMessage object represents a message in the forum" -- Cpt. Obvious """ - topic = models.ForeignKey(ForumTopic, related_name='messages') - author = models.ForeignKey(User, related_name='forum_messages') + + topic = models.ForeignKey(ForumTopic, related_name="messages") + author = models.ForeignKey(User, related_name="forum_messages") title = models.CharField(_("title"), default="", max_length=64, blank=True) message = models.TextField(_("message"), default="") - date = models.DateTimeField(_('date'), default=timezone.now) - readers = models.ManyToManyField(User, related_name="read_messages", verbose_name=_("readers")) - _deleted = models.BooleanField(_('is deleted'), default=False) + date = models.DateTimeField(_("date"), default=timezone.now) + readers = models.ManyToManyField( + User, related_name="read_messages", verbose_name=_("readers") + ) + _deleted = models.BooleanField(_("is deleted"), default=False) class Meta: - ordering = ['-date'] + ordering = ["-date"] def __str__(self): return "%s (%s) - %s" % (self.id, self.author, self.title) @@ -266,32 +318,46 @@ class ForumMessage(models.Model): self.topic.save() def is_first_in_topic(self): - return bool(self.id == self.topic.messages.order_by('date').first().id) + return bool(self.id == self.topic.messages.order_by("date").first().id) def is_last_in_topic(self): - return bool(self.id == self.topic.messages.order_by('date').last().id) + return bool(self.id == self.topic.messages.order_by("date").last().id) def is_owned_by(self, user): # Anyone can create a topic: it's better to - # check the rights at the forum level, since it's more controlled + # check the rights at the forum level, since it's more controlled return self.topic.forum.is_owned_by(user) or user.id == self.author.id def can_be_edited_by(self, user): return user.can_edit(self.topic.forum) def can_be_viewed_by(self, user): - return not self._deleted # No need to check the real rights since it's already done by the Topic view + return ( + not self._deleted + ) # No need to check the real rights since it's already done by the Topic view def can_be_moderated_by(self, user): return self.topic.forum.is_owned_by(user) or user.id == self.author.id def get_absolute_url(self): - return reverse('forum:view_message', kwargs={'message_id': self.id}) + return reverse("forum:view_message", kwargs={"message_id": self.id}) def get_url(self): - return self.topic.get_absolute_url() + "?page=" + str(self.get_page()) + "#msg_" + str(self.id) + return ( + self.topic.get_absolute_url() + + "?page=" + + str(self.get_page()) + + "#msg_" + + str(self.id) + ) def get_page(self): - return int(self.topic.messages.filter(id__lt=self.id).count() / settings.SITH_FORUM_PAGE_LENGTH) + 1 + return ( + int( + self.topic.messages.filter(id__lt=self.id).count() + / settings.SITH_FORUM_PAGE_LENGTH + ) + + 1 + ) def mark_as_read(self, user): try: # Need the try/except because of AnonymousUser @@ -301,26 +367,28 @@ class ForumMessage(models.Model): pass def is_read(self, user): - return (self.date < user.forum_infos.last_read_date) or (user in self.readers.all()) + return (self.date < user.forum_infos.last_read_date) or ( + user in self.readers.all() + ) def is_deleted(self): - meta = self.metas.exclude(action="EDIT").order_by('-date').first() + meta = self.metas.exclude(action="EDIT").order_by("-date").first() if meta: return meta.action == "DELETE" return False MESSAGE_META_ACTIONS = [ - ('EDIT', _("Message edited by")), - ('DELETE', _("Message deleted by")), - ('UNDELETE', _("Message undeleted by")), + ("EDIT", _("Message edited by")), + ("DELETE", _("Message deleted by")), + ("UNDELETE", _("Message undeleted by")), ] class ForumMessageMeta(models.Model): user = models.ForeignKey(User, related_name="forum_message_metas") message = models.ForeignKey(ForumMessage, related_name="metas") - date = models.DateTimeField(_('date'), default=timezone.now) + date = models.DateTimeField(_("date"), default=timezone.now) action = models.CharField(_("action"), choices=MESSAGE_META_ACTIONS, max_length=16) def save(self, *args, **kwargs): @@ -335,9 +403,14 @@ class ForumUserInfo(models.Model): However, this can be extended with lot of user preferences dedicated to a user, such as the favourite topics, the signature, and so on... """ + user = models.OneToOneField(User, related_name="_forum_infos") - last_read_date = models.DateTimeField(_('last read date'), default=datetime(year=settings.SITH_SCHOOL_START_YEAR, - month=1, day=1, tzinfo=pytz.UTC)) + last_read_date = models.DateTimeField( + _("last read date"), + default=datetime( + year=settings.SITH_SCHOOL_START_YEAR, month=1, day=1, tzinfo=pytz.UTC + ), + ) def __str__(self): return str(self.user) diff --git a/forum/urls.py b/forum/urls.py index 26558cf9..27ff5718 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -27,22 +27,59 @@ from django.conf.urls import url from forum.views import * urlpatterns = [ - url(r'^$', ForumMainView.as_view(), name='main'), - url(r'^new_forum$', ForumCreateView.as_view(), name='new_forum'), - url(r'^mark_all_as_read$', ForumMarkAllAsRead.as_view(), name='mark_all_as_read'), - url(r'^last_unread$', ForumLastUnread.as_view(), name='last_unread'), - url(r'^favorite_topics$', ForumFavoriteTopics.as_view(), name='favorite_topics'), - url(r'^(?P[0-9]+)$', ForumDetailView.as_view(), name='view_forum'), - url(r'^(?P[0-9]+)/edit$', ForumEditView.as_view(), name='edit_forum'), - url(r'^(?P[0-9]+)/delete$', ForumDeleteView.as_view(), name='delete_forum'), - url(r'^(?P[0-9]+)/new_topic$', ForumTopicCreateView.as_view(), name='new_topic'), - url(r'^topic/(?P[0-9]+)$', ForumTopicDetailView.as_view(), name='view_topic'), - url(r'^topic/(?P[0-9]+)/edit$', ForumTopicEditView.as_view(), name='edit_topic'), - url(r'^topic/(?P[0-9]+)/new_message$', ForumMessageCreateView.as_view(), name='new_message'), - url(r'^topic/(?P[0-9]+)/toggle_subscribe$', ForumTopicSubscribeView.as_view(), name='toggle_subscribe_topic'), - url(r'^message/(?P[0-9]+)$', ForumMessageView.as_view(), name='view_message'), - url(r'^message/(?P[0-9]+)/edit$', ForumMessageEditView.as_view(), name='edit_message'), - url(r'^message/(?P[0-9]+)/delete$', ForumMessageDeleteView.as_view(), name='delete_message'), - url(r'^message/(?P[0-9]+)/undelete$', ForumMessageUndeleteView.as_view(), name='undelete_message'), + url(r"^$", ForumMainView.as_view(), name="main"), + url(r"^new_forum$", ForumCreateView.as_view(), name="new_forum"), + url(r"^mark_all_as_read$", ForumMarkAllAsRead.as_view(), name="mark_all_as_read"), + url(r"^last_unread$", ForumLastUnread.as_view(), name="last_unread"), + url(r"^favorite_topics$", ForumFavoriteTopics.as_view(), name="favorite_topics"), + url(r"^(?P[0-9]+)$", ForumDetailView.as_view(), name="view_forum"), + url(r"^(?P[0-9]+)/edit$", ForumEditView.as_view(), name="edit_forum"), + url( + r"^(?P[0-9]+)/delete$", ForumDeleteView.as_view(), name="delete_forum" + ), + url( + r"^(?P[0-9]+)/new_topic$", + ForumTopicCreateView.as_view(), + name="new_topic", + ), + url( + r"^topic/(?P[0-9]+)$", + ForumTopicDetailView.as_view(), + name="view_topic", + ), + url( + r"^topic/(?P[0-9]+)/edit$", + ForumTopicEditView.as_view(), + name="edit_topic", + ), + url( + r"^topic/(?P[0-9]+)/new_message$", + ForumMessageCreateView.as_view(), + name="new_message", + ), + url( + r"^topic/(?P[0-9]+)/toggle_subscribe$", + ForumTopicSubscribeView.as_view(), + name="toggle_subscribe_topic", + ), + url( + r"^message/(?P[0-9]+)$", + ForumMessageView.as_view(), + name="view_message", + ), + url( + r"^message/(?P[0-9]+)/edit$", + ForumMessageEditView.as_view(), + name="edit_message", + ), + url( + r"^message/(?P[0-9]+)/delete$", + ForumMessageDeleteView.as_view(), + name="delete_message", + ), + url( + r"^message/(?P[0-9]+)/undelete$", + ForumMessageUndeleteView.as_view(), + name="undelete_message", + ), ] - diff --git a/forum/views.py b/forum/views.py index 73f8423c..946d563c 100644 --- a/forum/views.py +++ b/forum/views.py @@ -42,13 +42,15 @@ from forum.models import Forum, ForumMessage, ForumTopic, ForumMessageMeta class ForumMainView(ListView): - queryset = Forum.objects.filter(parent=None).prefetch_related("children___last_message__author", "children___last_message__topic") + queryset = Forum.objects.filter(parent=None).prefetch_related( + "children___last_message__author", "children___last_message__topic" + ) template_name = "forum/main.jinja" class ForumMarkAllAsRead(RedirectView): permanent = False - url = reverse_lazy('forum:last_unread') + url = reverse_lazy("forum:last_unread") def get(self, request, *args, **kwargs): try: @@ -78,11 +80,15 @@ class ForumLastUnread(ListView): paginate_by = settings.SITH_FORUM_PAGE_LENGTH / 2 def get_queryset(self): - topic_list = self.model.objects.filter(_last_message__date__gt=self.request.user.forum_infos.last_read_date)\ - .exclude(_last_message__readers=self.request.user)\ - .order_by('-_last_message__date')\ - .select_related('_last_message__author', 'author')\ - .prefetch_related('forum__edit_groups') + topic_list = ( + self.model.objects.filter( + _last_message__date__gt=self.request.user.forum_infos.last_read_date + ) + .exclude(_last_message__readers=self.request.user) + .order_by("-_last_message__date") + .select_related("_last_message__author", "author") + .prefetch_related("forum__edit_groups") + ) return topic_list @@ -94,9 +100,18 @@ class ForumNameField(forms.ModelChoiceField): class ForumForm(forms.ModelForm): class Meta: model = Forum - fields = ['name', 'parent', 'number', 'owner_club', 'is_category', 'edit_groups', 'view_groups'] - edit_groups = make_ajax_field(Forum, 'edit_groups', 'groups', help_text="") - view_groups = make_ajax_field(Forum, 'view_groups', 'groups', help_text="") + fields = [ + "name", + "parent", + "number", + "owner_club", + "is_category", + "edit_groups", + "view_groups", + ] + + edit_groups = make_ajax_field(Forum, "edit_groups", "groups", help_text="") + view_groups = make_ajax_field(Forum, "view_groups", "groups", help_text="") parent = ForumNameField(Forum.objects.all()) @@ -108,18 +123,20 @@ class ForumCreateView(CanCreateMixin, CreateView): def get_initial(self): init = super(ForumCreateView, self).get_initial() try: - parent = Forum.objects.filter(id=self.request.GET['parent']).first() - init['parent'] = parent - init['owner_club'] = parent.owner_club - init['edit_groups'] = parent.edit_groups.all() - init['view_groups'] = parent.view_groups.all() + parent = Forum.objects.filter(id=self.request.GET["parent"]).first() + init["parent"] = parent + init["owner_club"] = parent.owner_club + init["edit_groups"] = parent.edit_groups.all() + init["view_groups"] = parent.view_groups.all() except: pass return init class ForumEditForm(ForumForm): - recursive = forms.BooleanField(label=_("Apply rights and club owner recursively"), required=False) + recursive = forms.BooleanField( + label=_("Apply rights and club owner recursively"), required=False + ) class ForumEditView(CanEditPropMixin, UpdateView): @@ -127,11 +144,11 @@ class ForumEditView(CanEditPropMixin, UpdateView): pk_url_kwarg = "forum_id" form_class = ForumEditForm template_name = "core/edit.jinja" - success_url = reverse_lazy('forum:main') + success_url = reverse_lazy("forum:main") def form_valid(self, form): ret = super(ForumEditView, self).form_valid(form) - if form.cleaned_data['recursive']: + if form.cleaned_data["recursive"]: self.object.apply_rights_recursively() return ret @@ -140,7 +157,7 @@ class ForumDeleteView(CanEditPropMixin, DeleteView): model = Forum pk_url_kwarg = "forum_id" template_name = "core/delete_confirm.jinja" - success_url = reverse_lazy('forum:main') + success_url = reverse_lazy("forum:main") class ForumDetailView(CanViewMixin, DetailView): @@ -150,12 +167,13 @@ class ForumDetailView(CanViewMixin, DetailView): def get_context_data(self, **kwargs): kwargs = super(ForumDetailView, self).get_context_data(**kwargs) - qs = self.object.topics.order_by('-_last_message__date')\ - .select_related('_last_message__author', 'author')\ + qs = ( + self.object.topics.order_by("-_last_message__date") + .select_related("_last_message__author", "author") .prefetch_related("forum__edit_groups") - paginator = Paginator(qs, - settings.SITH_FORUM_PAGE_LENGTH) - page = self.request.GET.get('topic_page') + ) + paginator = Paginator(qs, settings.SITH_FORUM_PAGE_LENGTH) + page = self.request.GET.get("topic_page") try: kwargs["topics"] = paginator.page(page) except PageNotAnInteger: @@ -168,10 +186,9 @@ class ForumDetailView(CanViewMixin, DetailView): class TopicForm(forms.ModelForm): class Meta: model = ForumMessage - fields = ['title', 'message'] - widgets = { - 'message': MarkdownInput, - } + fields = ["title", "message"] + widgets = {"message": MarkdownInput} + title = forms.CharField(required=True, label=_("Title")) @@ -181,13 +198,17 @@ class ForumTopicCreateView(CanCreateMixin, CreateView): template_name = "forum/reply.jinja" def dispatch(self, request, *args, **kwargs): - self.forum = get_object_or_404(Forum, id=self.kwargs['forum_id'], is_category=False) + self.forum = get_object_or_404( + Forum, id=self.kwargs["forum_id"], is_category=False + ) if not request.user.can_view(self.forum): raise PermissionDenied return super(ForumTopicCreateView, self).dispatch(request, *args, **kwargs) def form_valid(self, form): - topic = ForumTopic(_title=form.instance.title, author=self.request.user, forum=self.forum) + topic = ForumTopic( + _title=form.instance.title, author=self.request.user, forum=self.forum + ) topic.save() form.instance.topic = topic form.instance.author = self.request.user @@ -196,10 +217,11 @@ class ForumTopicCreateView(CanCreateMixin, CreateView): class ForumTopicEditView(CanEditMixin, UpdateView): model = ForumTopic - fields = ['forum'] + fields = ["forum"] pk_url_kwarg = "topic_id" template_name = "core/edit.jinja" + class ForumTopicSubscribeView(CanViewMixin, SingleObjectMixin, RedirectView): model = ForumTopic pk_url_kwarg = "topic_id" @@ -222,19 +244,22 @@ class ForumTopicDetailView(CanViewMixin, DetailView): pk_url_kwarg = "topic_id" template_name = "forum/topic.jinja" context_object_name = "topic" - queryset = ForumTopic.objects.select_related('forum__parent') + queryset = ForumTopic.objects.select_related("forum__parent") def get_context_data(self, **kwargs): kwargs = super(ForumTopicDetailView, self).get_context_data(**kwargs) try: msg = self.object.get_first_unread_message(self.request.user) - kwargs['first_unread_message_id'] = msg.id + kwargs["first_unread_message_id"] = msg.id except: - kwargs['first_unread_message_id'] = float("inf") - paginator = Paginator(self.object.messages.select_related('author__avatar_pict') - .prefetch_related('topic__forum__edit_groups', 'readers').order_by('date'), - settings.SITH_FORUM_PAGE_LENGTH) - page = self.request.GET.get('page') + kwargs["first_unread_message_id"] = float("inf") + paginator = Paginator( + self.object.messages.select_related("author__avatar_pict") + .prefetch_related("topic__forum__edit_groups", "readers") + .order_by("date"), + settings.SITH_FORUM_PAGE_LENGTH, + ) + page = self.request.GET.get("page") try: kwargs["msgs"] = paginator.page(page) except PageNotAnInteger: @@ -256,17 +281,23 @@ class ForumMessageView(SingleObjectMixin, RedirectView): class ForumMessageEditView(CanEditMixin, UpdateView): model = ForumMessage - form_class = forms.modelform_factory(model=ForumMessage, fields=['title', 'message', ], widgets={'message': MarkdownInput}) + form_class = forms.modelform_factory( + model=ForumMessage, + fields=["title", "message"], + widgets={"message": MarkdownInput}, + ) template_name = "forum/reply.jinja" pk_url_kwarg = "message_id" def form_valid(self, form): - ForumMessageMeta(message=self.object, user=self.request.user, action="EDIT").save() + ForumMessageMeta( + message=self.object, user=self.request.user, action="EDIT" + ).save() return super(ForumMessageEditView, self).form_valid(form) def get_context_data(self, **kwargs): kwargs = super(ForumMessageEditView, self).get_context_data(**kwargs) - kwargs['topic'] = self.object.topic + kwargs["topic"] = self.object.topic return kwargs @@ -278,7 +309,9 @@ class ForumMessageDeleteView(SingleObjectMixin, RedirectView): def get_redirect_url(self, *args, **kwargs): self.object = self.get_object() if self.object.can_be_moderated_by(self.request.user): - ForumMessageMeta(message=self.object, user=self.request.user, action="DELETE").save() + ForumMessageMeta( + message=self.object, user=self.request.user, action="DELETE" + ).save() return self.object.get_absolute_url() @@ -290,17 +323,23 @@ class ForumMessageUndeleteView(SingleObjectMixin, RedirectView): def get_redirect_url(self, *args, **kwargs): self.object = self.get_object() if self.object.can_be_moderated_by(self.request.user): - ForumMessageMeta(message=self.object, user=self.request.user, action="UNDELETE").save() + ForumMessageMeta( + message=self.object, user=self.request.user, action="UNDELETE" + ).save() return self.object.get_absolute_url() class ForumMessageCreateView(CanCreateMixin, CreateView): model = ForumMessage - form_class = forms.modelform_factory(model=ForumMessage, fields=['title', 'message', ], widgets={'message': MarkdownInput}) + form_class = forms.modelform_factory( + model=ForumMessage, + fields=["title", "message"], + widgets={"message": MarkdownInput}, + ) template_name = "forum/reply.jinja" def dispatch(self, request, *args, **kwargs): - self.topic = get_object_or_404(ForumTopic, id=self.kwargs['topic_id']) + self.topic = get_object_or_404(ForumTopic, id=self.kwargs["topic_id"]) if not request.user.can_view(self.topic): raise PermissionDenied return super(ForumMessageCreateView, self).dispatch(request, *args, **kwargs) @@ -308,12 +347,18 @@ class ForumMessageCreateView(CanCreateMixin, CreateView): def get_initial(self): init = super(ForumMessageCreateView, self).get_initial() try: - message = ForumMessage.objects.select_related('author').filter(id=self.request.GET['quote_id']).first() - init['message'] = "> ##### %s\n" % (_("%(author)s said") % {'author': message.author.get_short_name()}) - init['message'] += "\n".join([ - "> " + line for line in message.message.split('\n') - ]) - init['message'] += "\n\n" + message = ( + ForumMessage.objects.select_related("author") + .filter(id=self.request.GET["quote_id"]) + .first() + ) + init["message"] = "> ##### %s\n" % ( + _("%(author)s said") % {"author": message.author.get_short_name()} + ) + init["message"] += "\n".join( + ["> " + line for line in message.message.split("\n")] + ) + init["message"] += "\n\n" except Exception as e: print(repr(e)) return init @@ -325,5 +370,5 @@ class ForumMessageCreateView(CanCreateMixin, CreateView): def get_context_data(self, **kwargs): kwargs = super(ForumMessageCreateView, self).get_context_data(**kwargs) - kwargs['topic'] = self.topic + kwargs["topic"] = self.topic return kwargs diff --git a/launderette/__init__.py b/launderette/__init__.py index 0a9419f8..0ace29c4 100644 --- a/launderette/__init__.py +++ b/launderette/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/launderette/migrations/0001_initial.py b/launderette/migrations/0001_initial.py index dc441a4b..c437d29d 100644 --- a/launderette/migrations/0001_initial.py +++ b/launderette/migrations/0001_initial.py @@ -6,76 +6,168 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0001_initial'), - ('counter', '0001_initial'), - ] + dependencies = [("subscription", "0001_initial"), ("counter", "0001_initial")] operations = [ migrations.CreateModel( - name='Launderette', + name="Launderette", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('counter', models.OneToOneField(related_name='launderette', verbose_name='counter', to='counter.Counter')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "counter", + models.OneToOneField( + related_name="launderette", + verbose_name="counter", + to="counter.Counter", + ), + ), ], - options={ - 'verbose_name': 'Launderette', - }, + options={"verbose_name": "Launderette"}, ), migrations.CreateModel( - name='Machine', + name="Machine", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('type', models.CharField(choices=[('WASHING', 'Washing'), ('DRYING', 'Drying')], max_length=10, verbose_name='type')), - ('is_working', models.BooleanField(verbose_name='is working', default=True)), - ('launderette', models.ForeignKey(verbose_name='launderette', to='launderette.Launderette', related_name='machines')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "type", + models.CharField( + choices=[("WASHING", "Washing"), ("DRYING", "Drying")], + max_length=10, + verbose_name="type", + ), + ), + ( + "is_working", + models.BooleanField(verbose_name="is working", default=True), + ), + ( + "launderette", + models.ForeignKey( + verbose_name="launderette", + to="launderette.Launderette", + related_name="machines", + ), + ), ], - options={ - 'verbose_name': 'Machine', - }, + options={"verbose_name": "Machine"}, ), migrations.CreateModel( - name='Slot', + name="Slot", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('start_date', models.DateTimeField(verbose_name='start date')), - ('type', models.CharField(choices=[('WASHING', 'Washing'), ('DRYING', 'Drying')], max_length=10, verbose_name='type')), - ('machine', models.ForeignKey(verbose_name='machine', to='launderette.Machine', related_name='slots')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("start_date", models.DateTimeField(verbose_name="start date")), + ( + "type", + models.CharField( + choices=[("WASHING", "Washing"), ("DRYING", "Drying")], + max_length=10, + verbose_name="type", + ), + ), + ( + "machine", + models.ForeignKey( + verbose_name="machine", + to="launderette.Machine", + related_name="slots", + ), + ), ], - options={ - 'verbose_name': 'Slot', - 'ordering': ['start_date'], - }, + options={"verbose_name": "Slot", "ordering": ["start_date"]}, ), migrations.CreateModel( - name='Token', + name="Token", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=5, verbose_name='name')), - ('type', models.CharField(choices=[('WASHING', 'Washing'), ('DRYING', 'Drying')], max_length=10, verbose_name='type')), - ('borrow_date', models.DateTimeField(null=True, verbose_name='borrow date', blank=True)), - ('launderette', models.ForeignKey(verbose_name='launderette', to='launderette.Launderette', related_name='tokens')), - ('user', models.ForeignKey(null=True, related_name='tokens', verbose_name='user', to='core.User', blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=5, verbose_name="name")), + ( + "type", + models.CharField( + choices=[("WASHING", "Washing"), ("DRYING", "Drying")], + max_length=10, + verbose_name="type", + ), + ), + ( + "borrow_date", + models.DateTimeField( + null=True, verbose_name="borrow date", blank=True + ), + ), + ( + "launderette", + models.ForeignKey( + verbose_name="launderette", + to="launderette.Launderette", + related_name="tokens", + ), + ), + ( + "user", + models.ForeignKey( + null=True, + related_name="tokens", + verbose_name="user", + to="core.User", + blank=True, + ), + ), ], - options={ - 'verbose_name': 'Token', - 'ordering': ['type', 'name'], - }, + options={"verbose_name": "Token", "ordering": ["type", "name"]}, ), migrations.AddField( - model_name='slot', - name='token', - field=models.ForeignKey(null=True, related_name='slots', verbose_name='token', to='launderette.Token', blank=True), + model_name="slot", + name="token", + field=models.ForeignKey( + null=True, + related_name="slots", + verbose_name="token", + to="launderette.Token", + blank=True, + ), ), migrations.AddField( - model_name='slot', - name='user', - field=models.ForeignKey(verbose_name='user', to='core.User', related_name='slots'), + model_name="slot", + name="user", + field=models.ForeignKey( + verbose_name="user", to="core.User", related_name="slots" + ), ), migrations.AlterUniqueTogether( - name='token', - unique_together=set([('name', 'launderette', 'type')]), + name="token", unique_together=set([("name", "launderette", "type")]) ), ] diff --git a/launderette/models.py b/launderette/models.py index 9528db70..0c92c8d7 100644 --- a/launderette/models.py +++ b/launderette/models.py @@ -35,24 +35,30 @@ from club.models import Club class Launderette(models.Model): - name = models.CharField(_('name'), max_length=30) - counter = models.OneToOneField(Counter, verbose_name=_('counter'), related_name='launderette') + name = models.CharField(_("name"), max_length=30) + counter = models.OneToOneField( + Counter, verbose_name=_("counter"), related_name="launderette" + ) class Meta: - verbose_name = _('Launderette') + verbose_name = _("Launderette") def is_owned_by(self, user): """ Method to see if that object can be edited by the given user """ - launderette_club = Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first() + launderette_club = Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ).first() m = launderette_club.get_membership_for(user) if m and m.role >= 9: return True return False def can_be_edited_by(self, user): - launderette_club = Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first() + launderette_club = Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ).first() m = launderette_club.get_membership_for(user) if m and m.role >= 2: return True @@ -65,7 +71,7 @@ class Launderette(models.Model): return self.name def get_absolute_url(self): - return reverse('launderette:launderette_list') + return reverse("launderette:launderette_list") def get_machine_list(self): return Machine.objects.filter(launderette_id=self.id) @@ -81,19 +87,25 @@ class Launderette(models.Model): class Machine(models.Model): - name = models.CharField(_('name'), max_length=30) - launderette = models.ForeignKey(Launderette, related_name='machines', verbose_name=_('launderette')) - type = models.CharField(_('type'), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES) - is_working = models.BooleanField(_('is working'), default=True) + name = models.CharField(_("name"), max_length=30) + launderette = models.ForeignKey( + Launderette, related_name="machines", verbose_name=_("launderette") + ) + type = models.CharField( + _("type"), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES + ) + is_working = models.BooleanField(_("is working"), default=True) class Meta: - verbose_name = _('Machine') + verbose_name = _("Machine") def is_owned_by(self, user): """ Method to see if that object can be edited by the given user """ - launderette_club = Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first() + launderette_club = Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ).first() m = launderette_club.get_membership_for(user) if m and m.role >= 9: return True @@ -103,20 +115,29 @@ class Machine(models.Model): return "%s %s" % (self._meta.verbose_name, self.name) def get_absolute_url(self): - return reverse('launderette:launderette_admin', kwargs={"launderette_id": self.launderette.id}) + return reverse( + "launderette:launderette_admin", + kwargs={"launderette_id": self.launderette.id}, + ) class Token(models.Model): - name = models.CharField(_('name'), max_length=5) - launderette = models.ForeignKey(Launderette, related_name='tokens', verbose_name=_('launderette')) - type = models.CharField(_('type'), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES) - borrow_date = models.DateTimeField(_('borrow date'), null=True, blank=True) - user = models.ForeignKey(User, related_name='tokens', verbose_name=_('user'), null=True, blank=True) + name = models.CharField(_("name"), max_length=5) + launderette = models.ForeignKey( + Launderette, related_name="tokens", verbose_name=_("launderette") + ) + type = models.CharField( + _("type"), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES + ) + borrow_date = models.DateTimeField(_("borrow date"), null=True, blank=True) + user = models.ForeignKey( + User, related_name="tokens", verbose_name=_("user"), null=True, blank=True + ) class Meta: - verbose_name = _('Token') - unique_together = ('name', 'launderette', 'type') - ordering = ['type', 'name'] + verbose_name = _("Token") + unique_together = ("name", "launderette", "type") + ordering = ["type", "name"] def save(self, *args, **kwargs): if self.name == "": @@ -128,14 +149,25 @@ class Token(models.Model): """ Method to see if that object can be edited by the given user """ - launderette_club = Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first() + launderette_club = Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ).first() m = launderette_club.get_membership_for(user) if m and m.role >= 9: return True return False def __str__(self): - return self.__class__._meta.verbose_name + " " + self.get_type_display() + " #" + self.name + " (" + self.launderette.name + ")" + return ( + self.__class__._meta.verbose_name + + " " + + self.get_type_display() + + " #" + + self.name + + " (" + + self.launderette.name + + ")" + ) def is_avaliable(self): if not self.borrow_date and not self.user: @@ -145,19 +177,30 @@ class Token(models.Model): class Slot(models.Model): - start_date = models.DateTimeField(_('start date')) - type = models.CharField(_('type'), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES) - machine = models.ForeignKey(Machine, related_name='slots', verbose_name=_('machine')) - token = models.ForeignKey(Token, related_name='slots', verbose_name=_('token'), blank=True, null=True) - user = models.ForeignKey(User, related_name='slots', verbose_name=_('user')) + start_date = models.DateTimeField(_("start date")) + type = models.CharField( + _("type"), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES + ) + machine = models.ForeignKey( + Machine, related_name="slots", verbose_name=_("machine") + ) + token = models.ForeignKey( + Token, related_name="slots", verbose_name=_("token"), blank=True, null=True + ) + user = models.ForeignKey(User, related_name="slots", verbose_name=_("user")) class Meta: - verbose_name = _('Slot') - ordering = ['start_date'] + verbose_name = _("Slot") + ordering = ["start_date"] def is_owned_by(self, user): return user == self.user def __str__(self): - return "User: %s - Date: %s - Type: %s - Machine: %s - Token: %s" % (self.user, self.start_date, self.get_type_display(), - self.machine.name, self.token) + return "User: %s - Date: %s - Type: %s - Machine: %s - Token: %s" % ( + self.user, + self.start_date, + self.get_type_display(), + self.machine.name, + self.token, + ) diff --git a/launderette/urls.py b/launderette/urls.py index 844f1385..35baabb1 100644 --- a/launderette/urls.py +++ b/launderette/urls.py @@ -28,20 +28,49 @@ from launderette.views import * urlpatterns = [ # views - url(r'^$', LaunderetteMainView.as_view(), name='launderette_main'), - url(r'^slot/(?P[0-9]+)/delete$', SlotDeleteView.as_view(), name='delete_slot'), - url(r'^book$', LaunderetteBookMainView.as_view(), name='book_main'), - url(r'^book/(?P[0-9]+)$', LaunderetteBookView.as_view(), name='book_slot'), - url(r'^(?P[0-9]+)/click$', LaunderetteMainClickView.as_view(), name='main_click'), - url(r'^(?P[0-9]+)/click/(?P[0-9]+)$', LaunderetteClickView.as_view(), name='click'), - url(r'^admin$', LaunderetteListView.as_view(), name='launderette_list'), - url(r'^admin/(?P[0-9]+)$', LaunderetteAdminView.as_view(), name='launderette_admin'), - url(r'^admin/(?P[0-9]+)/edit$', LaunderetteEditView.as_view(), name='launderette_edit'), - url(r'^admin/new$', LaunderetteCreateView.as_view(), name='launderette_new'), - url(r'^admin/machine/new$', MachineCreateView.as_view(), name='machine_new'), - url(r'^admin/machine/(?P[0-9]+)/edit$', MachineEditView.as_view(), name='machine_edit'), - url(r'^admin/machine/(?P[0-9]+)/delete$', MachineDeleteView.as_view(), name='machine_delete'), + url(r"^$", LaunderetteMainView.as_view(), name="launderette_main"), + url( + r"^slot/(?P[0-9]+)/delete$", + SlotDeleteView.as_view(), + name="delete_slot", + ), + url(r"^book$", LaunderetteBookMainView.as_view(), name="book_main"), + url( + r"^book/(?P[0-9]+)$", + LaunderetteBookView.as_view(), + name="book_slot", + ), + url( + r"^(?P[0-9]+)/click$", + LaunderetteMainClickView.as_view(), + name="main_click", + ), + url( + r"^(?P[0-9]+)/click/(?P[0-9]+)$", + LaunderetteClickView.as_view(), + name="click", + ), + url(r"^admin$", LaunderetteListView.as_view(), name="launderette_list"), + url( + r"^admin/(?P[0-9]+)$", + LaunderetteAdminView.as_view(), + name="launderette_admin", + ), + url( + r"^admin/(?P[0-9]+)/edit$", + LaunderetteEditView.as_view(), + name="launderette_edit", + ), + url(r"^admin/new$", LaunderetteCreateView.as_view(), name="launderette_new"), + url(r"^admin/machine/new$", MachineCreateView.as_view(), name="machine_new"), + url( + r"^admin/machine/(?P[0-9]+)/edit$", + MachineEditView.as_view(), + name="machine_edit", + ), + url( + r"^admin/machine/(?P[0-9]+)/delete$", + MachineDeleteView.as_view(), + name="machine_delete", + ), ] - - - diff --git a/launderette/views.py b/launderette/views.py index da7e6219..0b920ac1 100644 --- a/launderette/views.py +++ b/launderette/views.py @@ -48,26 +48,29 @@ from counter.views import GetUserForm class LaunderetteMainView(TemplateView): """Main presentation view""" - template_name = 'launderette/launderette_main.jinja' + + template_name = "launderette/launderette_main.jinja" def get_context_data(self, **kwargs): """ Add page to the context """ kwargs = super(LaunderetteMainView, self).get_context_data(**kwargs) - kwargs['page'] = Page.objects.filter(name='launderette').first() + kwargs["page"] = Page.objects.filter(name="launderette").first() return kwargs class LaunderetteBookMainView(CanViewMixin, ListView): """Choose which launderette to book""" + model = Launderette - template_name = 'launderette/launderette_book_choose.jinja' + template_name = "launderette/launderette_book_choose.jinja" class LaunderetteBookView(CanViewMixin, DetailView): """Display the launderette schedule""" + model = Launderette pk_url_kwarg = "launderette_id" - template_name = 'launderette/launderette_book.jinja' + template_name = "launderette/launderette_book.jinja" def get(self, request, *args, **kwargs): self.slot_type = "BOTH" @@ -79,23 +82,46 @@ class LaunderetteBookView(CanViewMixin, DetailView): self.machines = {} with transaction.atomic(): self.object = self.get_object() - if 'slot_type' in request.POST.keys(): - self.slot_type = request.POST['slot_type'] - if 'slot' in request.POST.keys() and request.user.is_authenticated(): + if "slot_type" in request.POST.keys(): + self.slot_type = request.POST["slot_type"] + if "slot" in request.POST.keys() and request.user.is_authenticated(): self.subscriber = request.user if self.subscriber.is_subscribed: - self.date = dateparse.parse_datetime(request.POST['slot']).replace(tzinfo=pytz.UTC) + self.date = dateparse.parse_datetime(request.POST["slot"]).replace( + tzinfo=pytz.UTC + ) if self.slot_type == "WASHING": if self.check_slot(self.slot_type): - Slot(user=self.subscriber, start_date=self.date, machine=self.machines[self.slot_type], type=self.slot_type).save() + Slot( + user=self.subscriber, + start_date=self.date, + machine=self.machines[self.slot_type], + type=self.slot_type, + ).save() elif self.slot_type == "DRYING": if self.check_slot(self.slot_type): - Slot(user=self.subscriber, start_date=self.date, machine=self.machines[self.slot_type], type=self.slot_type).save() + Slot( + user=self.subscriber, + start_date=self.date, + machine=self.machines[self.slot_type], + type=self.slot_type, + ).save() else: - if self.check_slot("WASHING") and self.check_slot("DRYING", self.date + timedelta(hours=1)): - Slot(user=self.subscriber, start_date=self.date, machine=self.machines["WASHING"], type="WASHING").save() - Slot(user=self.subscriber, start_date=self.date + timedelta(hours=1), - machine=self.machines["DRYING"], type="DRYING").save() + if self.check_slot("WASHING") and self.check_slot( + "DRYING", self.date + timedelta(hours=1) + ): + Slot( + user=self.subscriber, + start_date=self.date, + machine=self.machines["WASHING"], + type="WASHING", + ).save() + Slot( + user=self.subscriber, + start_date=self.date + timedelta(hours=1), + machine=self.machines["DRYING"], + type="DRYING", + ).save() return super(LaunderetteBookView, self).get(request, *args, **kwargs) def check_slot(self, type, date=None): @@ -118,31 +144,42 @@ class LaunderetteBookView(CanViewMixin, DetailView): def get_context_data(self, **kwargs): """ Add page to the context """ kwargs = super(LaunderetteBookView, self).get_context_data(**kwargs) - kwargs['planning'] = OrderedDict() - kwargs['slot_type'] = self.slot_type - start_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=pytz.UTC) - for date in LaunderetteBookView.date_iterator(start_date, start_date + timedelta(days=6), timedelta(days=1)): - kwargs['planning'][date] = [] - for h in LaunderetteBookView.date_iterator(date, date + timedelta(days=1), timedelta(hours=1)): + kwargs["planning"] = OrderedDict() + kwargs["slot_type"] = self.slot_type + start_date = datetime.now().replace( + hour=0, minute=0, second=0, microsecond=0, tzinfo=pytz.UTC + ) + for date in LaunderetteBookView.date_iterator( + start_date, start_date + timedelta(days=6), timedelta(days=1) + ): + kwargs["planning"][date] = [] + for h in LaunderetteBookView.date_iterator( + date, date + timedelta(days=1), timedelta(hours=1) + ): free = False - if self.slot_type == "BOTH" and self.check_slot("WASHING", h) and self.check_slot("DRYING", h + timedelta(hours=1)): + if ( + self.slot_type == "BOTH" + and self.check_slot("WASHING", h) + and self.check_slot("DRYING", h + timedelta(hours=1)) + ): free = True elif self.slot_type == "WASHING" and self.check_slot("WASHING", h): free = True elif self.slot_type == "DRYING" and self.check_slot("DRYING", h): free = True if free and datetime.now().replace(tzinfo=pytz.UTC) < h: - kwargs['planning'][date].append(h) + kwargs["planning"][date].append(h) else: - kwargs['planning'][date].append(None) + kwargs["planning"][date].append(None) return kwargs class SlotDeleteView(CanEditPropMixin, DeleteView): """Delete a slot""" + model = Slot pk_url_kwarg = "slot_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def get_success_url(self): return self.request.user.get_absolute_url() @@ -150,76 +187,108 @@ class SlotDeleteView(CanEditPropMixin, DeleteView): # For admins + class LaunderetteListView(CanEditPropMixin, ListView): """Choose which launderette to administer""" + model = Launderette - template_name = 'launderette/launderette_list.jinja' + template_name = "launderette/launderette_list.jinja" class LaunderetteEditView(CanEditPropMixin, UpdateView): """Edit a launderette""" + model = Launderette pk_url_kwarg = "launderette_id" - fields = ['name'] - template_name = 'core/edit.jinja' + fields = ["name"] + template_name = "core/edit.jinja" class LaunderetteCreateView(CanCreateMixin, CreateView): """Create a new launderette""" + model = Launderette - fields = ['name'] - template_name = 'core/create.jinja' + fields = ["name"] + template_name = "core/create.jinja" def form_valid(self, form): - club = Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first() - c = Counter(name=form.instance.name, club=club, type='OFFICE') + club = Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ).first() + c = Counter(name=form.instance.name, club=club, type="OFFICE") c.save() form.instance.counter = c return super(LaunderetteCreateView, self).form_valid(form) class ManageTokenForm(forms.Form): - action = forms.ChoiceField(choices=[("BACK", _("Back")), ("ADD", _("Add")), ("DEL", _("Delete"))], initial="BACK", - label=_("Action"), widget=forms.RadioSelect) - token_type = forms.ChoiceField(choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES, label=_("Type"), initial="WASHING", - widget=forms.RadioSelect) - tokens = forms.CharField(max_length=512, widget=forms.widgets.Textarea, label=_("Tokens, separated by spaces")) + action = forms.ChoiceField( + choices=[("BACK", _("Back")), ("ADD", _("Add")), ("DEL", _("Delete"))], + initial="BACK", + label=_("Action"), + widget=forms.RadioSelect, + ) + token_type = forms.ChoiceField( + choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES, + label=_("Type"), + initial="WASHING", + widget=forms.RadioSelect, + ) + tokens = forms.CharField( + max_length=512, + widget=forms.widgets.Textarea, + label=_("Tokens, separated by spaces"), + ) def process(self, launderette): cleaned_data = self.cleaned_data - token_list = cleaned_data['tokens'].strip(" \n\r").split(" ") - token_type = cleaned_data['token_type'] + token_list = cleaned_data["tokens"].strip(" \n\r").split(" ") + token_type = cleaned_data["token_type"] self.data = {} - if cleaned_data['action'] == "BACK": + if cleaned_data["action"] == "BACK": for t in token_list: try: - tok = Token.objects.filter(launderette=launderette, type=token_type, name=t).first() + tok = Token.objects.filter( + launderette=launderette, type=token_type, name=t + ).first() tok.borrow_date = None tok.user = None tok.save() except: - self.add_error(None, _("Token %(token_name)s does not exists") % {'token_name': t}) - elif cleaned_data['action'] == "ADD": + self.add_error( + None, + _("Token %(token_name)s does not exists") % {"token_name": t}, + ) + elif cleaned_data["action"] == "ADD": for t in token_list: try: Token(launderette=launderette, type=token_type, name=t).save() except DataError as e: self.add_error(None, e) except: - self.add_error(None, _("Token %(token_name)s already exists") % {'token_name': t}) - elif cleaned_data['action'] == "DEL": + self.add_error( + None, + _("Token %(token_name)s already exists") % {"token_name": t}, + ) + elif cleaned_data["action"] == "DEL": for t in token_list: try: - Token.objects.filter(launderette=launderette, type=token_type, name=t).delete() + Token.objects.filter( + launderette=launderette, type=token_type, name=t + ).delete() except: - self.add_error(None, _("Token %(token_name)s does not exists") % {'token_name': t}) + self.add_error( + None, + _("Token %(token_name)s does not exists") % {"token_name": t}, + ) class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView): """The admin page of the launderette""" + model = Launderette pk_url_kwarg = "launderette_id" - template_name = 'launderette/launderette_admin.jinja' + template_name = "launderette/launderette_admin.jinja" form_class = ManageTokenForm def get(self, request, *args, **kwargs): @@ -252,17 +321,19 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView): """ kwargs = super(LaunderetteAdminView, self).get_context_data(**kwargs) if self.request.method == "GET": - kwargs['form'] = self.get_form() + kwargs["form"] = self.get_form() return kwargs def get_success_url(self): - return reverse_lazy('launderette:launderette_admin', args=self.args, kwargs=self.kwargs) + return reverse_lazy( + "launderette:launderette_admin", args=self.args, kwargs=self.kwargs + ) class GetLaunderetteUserForm(GetUserForm): def clean(self): cleaned_data = super(GetLaunderetteUserForm, self).clean() - sub = cleaned_data['user'] + sub = cleaned_data["user"] if sub.slots.all().count() <= 0: raise forms.ValidationError(_("User has booked no slot")) return cleaned_data @@ -270,10 +341,13 @@ class GetLaunderetteUserForm(GetUserForm): class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView): """The click page of the launderette""" + model = Launderette pk_url_kwarg = "launderette_id" - template_name = 'counter/counter_main.jinja' - form_class = GetLaunderetteUserForm # Form to enter a client code and get the corresponding user id + template_name = "counter/counter_main.jinja" + form_class = ( + GetLaunderetteUserForm + ) # Form to enter a client code and get the corresponding user id def get(self, request, *args, **kwargs): self.object = self.get_object() @@ -287,7 +361,7 @@ class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView): """ We handle here the redirection, passing the user id of the asked customer """ - self.kwargs['user_id'] = form.cleaned_data['user_id'] + self.kwargs["user_id"] = form.cleaned_data["user_id"] return super(LaunderetteMainClickView, self).form_valid(form) def get_context_data(self, **kwargs): @@ -295,18 +369,20 @@ class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView): We handle here the login form for the barman """ kwargs = super(LaunderetteMainClickView, self).get_context_data(**kwargs) - kwargs['counter'] = self.object.counter - kwargs['form'] = self.get_form() - kwargs['barmen'] = [self.request.user] - if 'last_basket' in self.request.session.keys(): - kwargs['last_basket'] = self.request.session.pop('last_basket', None) - kwargs['last_customer'] = self.request.session.pop('last_customer', None) - kwargs['last_total'] = self.request.session.pop('last_total', None) - kwargs['new_customer_amount'] = self.request.session.pop('new_customer_amount', None) + kwargs["counter"] = self.object.counter + kwargs["form"] = self.get_form() + kwargs["barmen"] = [self.request.user] + if "last_basket" in self.request.session.keys(): + kwargs["last_basket"] = self.request.session.pop("last_basket", None) + kwargs["last_customer"] = self.request.session.pop("last_customer", None) + kwargs["last_total"] = self.request.session.pop("last_total", None) + kwargs["new_customer_amount"] = self.request.session.pop( + "new_customer_amount", None + ) return kwargs def get_success_url(self): - return reverse_lazy('launderette:click', args=self.args, kwargs=self.kwargs) + return reverse_lazy("launderette:click", args=self.args, kwargs=self.kwargs) class ClickTokenForm(forms.BaseForm): @@ -317,8 +393,8 @@ class ClickTokenForm(forms.BaseForm): counter = Counter.objects.filter(id=self.counter_id).first() subscriber = customer.user self.last_basket = { - 'last_basket': [], - 'last_customer': customer.user.get_display_name(), + "last_basket": [], + "last_customer": customer.user.get_display_name(), } total = 0 for k, t in self.cleaned_data.items(): @@ -331,50 +407,77 @@ class ClickTokenForm(forms.BaseForm): t.borrow_date = datetime.now().replace(tzinfo=pytz.UTC) t.save() price = settings.SITH_LAUNDERETTE_PRICES[t.type] - s = Selling(label="Jeton " + t.get_type_display() + " N°" + t.name, club=counter.club, product=None, counter=counter, unit_price=price, - quantity=1, seller=operator, customer=customer) + s = Selling( + label="Jeton " + t.get_type_display() + " N°" + t.name, + club=counter.club, + product=None, + counter=counter, + unit_price=price, + quantity=1, + seller=operator, + customer=customer, + ) s.save() total += price - self.last_basket['last_basket'].append("Jeton " + t.get_type_display() + " N°" + t.name) - self.last_basket['new_customer_amount'] = str(customer.amount) - self.last_basket['last_total'] = str(total) + self.last_basket["last_basket"].append( + "Jeton " + t.get_type_display() + " N°" + t.name + ) + self.last_basket["new_customer_amount"] = str(customer.amount) + self.last_basket["last_total"] = str(total) return self.cleaned_data class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): """The click page of the launderette""" + model = Launderette pk_url_kwarg = "launderette_id" - template_name = 'launderette/launderette_click.jinja' + template_name = "launderette/launderette_click.jinja" def get_form_class(self): fields = OrderedDict() kwargs = {} + def clean_field_factory(field_name, slot): def clean_field(self2): t_name = str(self2.data[field_name]) if t_name != "": - t = Token.objects.filter(name=str(self2.data[field_name]), type=slot.type, launderette=self.object, - user=None).first() + t = Token.objects.filter( + name=str(self2.data[field_name]), + type=slot.type, + launderette=self.object, + user=None, + ).first() if t is None: raise forms.ValidationError(_("Token not found")) return t + return clean_field - for s in self.subscriber.slots.filter(token=None, start_date__gte=timezone.now().replace(tzinfo=None)).all(): + + for s in self.subscriber.slots.filter( + token=None, start_date__gte=timezone.now().replace(tzinfo=None) + ).all(): field_name = "slot-%s" % (str(s.id)) - fields[field_name] = forms.CharField(max_length=5, required=False, - label="%s - %s" % (s.get_type_display(), defaultfilters.date(s.start_date, "j N Y H:i"))) + fields[field_name] = forms.CharField( + max_length=5, + required=False, + label="%s - %s" + % ( + s.get_type_display(), + defaultfilters.date(s.start_date, "j N Y H:i"), + ), + ) # XXX l10n settings.DATETIME_FORMAT didn't work here :/ kwargs["clean_" + field_name] = clean_field_factory(field_name, s) - kwargs['subscriber_id'] = self.subscriber.id - kwargs['counter_id'] = self.object.counter.id - kwargs['operator_id'] = self.operator.id - kwargs['base_fields'] = fields - return type('ClickForm', (ClickTokenForm,), kwargs) + kwargs["subscriber_id"] = self.subscriber.id + kwargs["counter_id"] = self.object.counter.id + kwargs["operator_id"] = self.operator.id + kwargs["base_fields"] = fields + return type("ClickForm", (ClickTokenForm,), kwargs) def get(self, request, *args, **kwargs): """Simple get view""" - self.customer = Customer.objects.filter(user__id=self.kwargs['user_id']).first() + self.customer = Customer.objects.filter(user__id=self.kwargs["user_id"]).first() self.subscriber = self.customer.user self.operator = request.user return super(LaunderetteClickView, self).get(request, *args, **kwargs) @@ -382,7 +485,7 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): def post(self, request, *args, **kwargs): """ Handle the many possibilities of the post request """ self.object = self.get_object() - self.customer = Customer.objects.filter(user__id=self.kwargs['user_id']).first() + self.customer = Customer.objects.filter(user__id=self.kwargs["user_id"]).first() self.subscriber = self.customer.user self.operator = request.user return super(LaunderetteClickView, self).post(request, *args, **kwargs) @@ -399,43 +502,50 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): We handle here the login form for the barman """ kwargs = super(LaunderetteClickView, self).get_context_data(**kwargs) - if 'form' not in kwargs.keys(): - kwargs['form'] = self.get_form() - kwargs['counter'] = self.object.counter - kwargs['customer'] = self.customer + if "form" not in kwargs.keys(): + kwargs["form"] = self.get_form() + kwargs["counter"] = self.object.counter + kwargs["customer"] = self.customer return kwargs def get_success_url(self): - self.kwargs.pop('user_id', None) - return reverse_lazy('launderette:main_click', args=self.args, kwargs=self.kwargs) + self.kwargs.pop("user_id", None) + return reverse_lazy( + "launderette:main_click", args=self.args, kwargs=self.kwargs + ) class MachineEditView(CanEditPropMixin, UpdateView): """Edit a machine""" + model = Machine pk_url_kwarg = "machine_id" - fields = ['name', 'launderette', 'type', 'is_working'] - template_name = 'core/edit.jinja' + fields = ["name", "launderette", "type", "is_working"] + template_name = "core/edit.jinja" class MachineDeleteView(CanEditPropMixin, DeleteView): """Edit a machine""" + model = Machine pk_url_kwarg = "machine_id" - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('launderette:launderette_list') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("launderette:launderette_list") class MachineCreateView(CanCreateMixin, CreateView): """Create a new machine""" + model = Machine - fields = ['name', 'launderette', 'type'] - template_name = 'core/create.jinja' + fields = ["name", "launderette", "type"] + template_name = "core/create.jinja" def get_initial(self): ret = super(MachineCreateView, self).get_initial() - if 'launderette' in self.request.GET.keys(): - obj = Launderette.objects.filter(id=int(self.request.GET['launderette'])).first() + if "launderette" in self.request.GET.keys(): + obj = Launderette.objects.filter( + id=int(self.request.GET["launderette"]) + ).first() if obj is not None: - ret['launderette'] = obj.id + ret["launderette"] = obj.id return ret diff --git a/matmat/urls.py b/matmat/urls.py index 24557952..1959553d 100644 --- a/matmat/urls.py +++ b/matmat/urls.py @@ -27,8 +27,8 @@ from django.conf.urls import url from matmat.views import * urlpatterns = [ - url(r'^$', SearchNormalFormView.as_view(), name="search"), - url(r'^reverse$', SearchReverseFormView.as_view(), name="search_reverse"), - url(r'^quick$', SearchQuickFormView.as_view(), name="search_quick"), - url(r'^clear$', SearchClearFormView.as_view(), name="search_clear"), + url(r"^$", SearchNormalFormView.as_view(), name="search"), + url(r"^reverse$", SearchReverseFormView.as_view(), name="search_reverse"), + url(r"^quick$", SearchQuickFormView.as_view(), name="search_quick"), + url(r"^clear$", SearchClearFormView.as_view(), name="search_clear"), ] diff --git a/matmat/views.py b/matmat/views.py index 83961796..e1a541d0 100644 --- a/matmat/views.py +++ b/matmat/views.py @@ -54,28 +54,29 @@ class SearchForm(forms.ModelForm): class Meta: model = User fields = [ - 'first_name', - 'last_name', - 'nick_name', - 'role', - 'department', - 'semester', - 'promo', - 'date_of_birth', - 'phone', + "first_name", + "last_name", + "nick_name", + "role", + "department", + "semester", + "promo", + "date_of_birth", + "phone", ] widgets = { - 'date_of_birth': SelectDate, - 'phone': PhoneNumberInternationalFallbackWidget, + "date_of_birth": SelectDate, + "phone": PhoneNumberInternationalFallbackWidget, } - sex = forms.ChoiceField([ - ("MAN", _("Man")), - ("WOMAN", _("Woman")), - ("INDIFFERENT", _("Indifferent")) - ], widget=forms.RadioSelect, initial="INDIFFERENT", label=_('Sex')) + sex = forms.ChoiceField( + [("MAN", _("Man")), ("WOMAN", _("Woman")), ("INDIFFERENT", _("Indifferent"))], + widget=forms.RadioSelect, + initial="INDIFFERENT", + label=_("Sex"), + ) - quick = forms.CharField(label=_('Last/First name or nickname'), max_length=255) + quick = forms.CharField(label=_("Last/First name or nickname"), max_length=255) def __init__(self, *args, **kwargs): super(SearchForm, self).__init__(*args, **kwargs) @@ -86,10 +87,11 @@ class SearchForm(forms.ModelForm): def cleaned_data_json(self): data = self.cleaned_data for key in data.keys(): - if key in ('date_of_birth', 'phone') and data[key] is not None: + if key in ("date_of_birth", "phone") and data[key] is not None: data[key] = str(data[key]) return data + # Views @@ -97,16 +99,16 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView): model = User ordering = ["-id"] paginate_by = 12 - template_name = 'matmat/search_form.jinja' + template_name = "matmat/search_form.jinja" def dispatch(self, request, *args, **kwargs): - self.form_class = kwargs['form'] - self.search_type = kwargs['search_type'] + self.form_class = kwargs["form"] + self.search_type = kwargs["search_type"] self.session = request.session - self.last_search = self.session.get('matmat_search_result', str([])) + self.last_search = self.session.get("matmat_search_result", str([])) self.last_search = literal_eval(self.last_search) - if 'valid_form' in kwargs.keys(): - self.valid_form = kwargs['valid_form'] + if "valid_form" in kwargs.keys(): + self.valid_form = kwargs["valid_form"] else: self.valid_form = None @@ -124,18 +126,18 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView): def get_context_data(self, **kwargs): self.object = None kwargs = super(SearchFormListView, self).get_context_data(**kwargs) - kwargs['form'] = self.form_class - kwargs['result_exists'] = self.result_exists + kwargs["form"] = self.form_class + kwargs["result_exists"] = self.result_exists return kwargs def get_queryset(self): q = self.init_query if self.valid_form is not None: if self.search_type == SearchType.REVERSE: - q = q.filter(phone=self.valid_form['phone']).all() + q = q.filter(phone=self.valid_form["phone"]).all() elif self.search_type == SearchType.QUICK: - if self.valid_form['quick'].strip(): - q = search_user(self.valid_form['quick']) + if self.valid_form["quick"].strip(): + q = search_user(self.valid_form["quick"]) else: q = [] if not self.can_see_hidden and len(q) > 0: @@ -143,7 +145,9 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView): else: search_dict = {} for key, value in self.valid_form.items(): - if key not in ('phone', 'quick') and not (value == '' or value is None or value == 'INDIFFERENT'): + if key not in ("phone", "quick") and not ( + value == "" or value is None or value == "INDIFFERENT" + ): search_dict[key + "__icontains"] = value q = q.filter(**search_dict).all() else: @@ -155,7 +159,7 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView): self.last_search = [] for user in q: self.last_search.append(user.id) - self.session['matmat_search_result'] = str(self.last_search) + self.session["matmat_search_result"] = str(self.last_search) return q @@ -163,13 +167,14 @@ class SearchFormView(FormerSubscriberMixin, FormView): """ Allows users to search inside the user list """ + form_class = SearchForm def dispatch(self, request, *args, **kwargs): self.session = request.session self.init_query = User.objects - kwargs['form'] = self.get_form() - kwargs['search_type'] = self.search_type + kwargs["form"] = self.get_form() + kwargs["search_type"] = self.search_type return super(SearchFormView, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): @@ -180,14 +185,14 @@ class SearchFormView(FormerSubscriberMixin, FormView): form = self.get_form() view = SearchFormListView.as_view() if form.is_valid(): - kwargs['valid_form'] = form.clean() - request.session['matmat_search_form'] = form.cleaned_data_json + kwargs["valid_form"] = form.clean() + request.session["matmat_search_form"] = form.cleaned_data_json return view(request, *args, **kwargs) def get_initial(self): - init = self.session.get('matmat_search_form', {}) + init = self.session.get("matmat_search_form", {}) if not init: - init['department'] = '' + init["department"] = "" return init @@ -210,8 +215,8 @@ class SearchClearFormView(FormerSubscriberMixin, View): def dispatch(self, request, *args, **kwargs): super(SearchClearFormView, self).dispatch(request, *args, **kwargs) - if 'matmat_search_form' in request.session.keys(): - request.session.pop('matmat_search_form') - if 'matmat_search_result' in request.session.keys(): - request.session.pop('matmat_search_result') - return HttpResponseRedirect(reverse('matmat:search')) + if "matmat_search_form" in request.session.keys(): + request.session.pop("matmat_search_form") + if "matmat_search_result" in request.session.keys(): + request.session.pop("matmat_search_result") + return HttpResponseRedirect(reverse("matmat:search")) diff --git a/migrate.py b/migrate.py index cf424b51..8abe62e8 100644 --- a/migrate.py +++ b/migrate.py @@ -33,7 +33,7 @@ from pytz import timezone from os import listdir os.environ["DJANGO_SETTINGS_MODULE"] = "sith.settings" -os.environ['DJANGO_COLORS'] = 'nocolor' +os.environ["DJANGO_COLORS"] = "nocolor" django.setup() from django.db import IntegrityError @@ -47,67 +47,93 @@ from django.core.files import File from core.models import User, SithFile from core.utils import doku_to_markdown, bbcode_to_markdown from club.models import Club, Membership, Mailing, MailingSubscription -from counter.models import Customer, Counter, Selling, Refilling, Product, ProductType, Permanency, Eticket +from counter.models import ( + Customer, + Counter, + Selling, + Refilling, + Product, + ProductType, + Permanency, + Eticket, +) from subscription.models import Subscription from eboutic.models import Invoice, InvoiceItem -from accounting.models import BankAccount, ClubAccount, GeneralJournal, Operation, AccountingType, Company, SimplifiedAccountingType, Label +from accounting.models import ( + BankAccount, + ClubAccount, + GeneralJournal, + Operation, + AccountingType, + Company, + SimplifiedAccountingType, + Label, +) from sas.models import Album, Picture, PeoplePictureRelation -from forum.models import Forum, ForumTopic, ForumMessage, ForumMessageMeta, ForumUserInfo +from forum.models import ( + Forum, + ForumTopic, + ForumMessage, + ForumMessageMeta, + ForumUserInfo, +) db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) start = datetime.datetime.now() + def reset_index(*args): sqlcmd = StringIO() call_command("sqlsequencereset", *args, stdout=sqlcmd) cursor = connection.cursor() cursor.execute(sqlcmd.getvalue()) + def to_unicode(s): if s: - return bytes(s, 'cp1252', errors="replace").decode('utf-8', errors='replace') + return bytes(s, "cp1252", errors="replace").decode("utf-8", errors="replace") return "" def migrate_core(): def migrate_users(): - SEX = {'1': 'MAN', '2': 'WOMAN', None: 'MAN'} + SEX = {"1": "MAN", "2": "WOMAN", None: "MAN"} TSHIRT = { - None: '-', - '': '-', - 'NULL': '-', - 'XS': 'XS', - 'S': 'S', - 'M': 'M', - 'L': 'L', - 'XL': 'XL', - 'XXL': 'XXL', - 'XXXL': 'XXXL', - } + None: "-", + "": "-", + "NULL": "-", + "XS": "XS", + "S": "S", + "M": "M", + "L": "L", + "XL": "XL", + "XXL": "XXL", + "XXXL": "XXXL", + } ROLE = { - 'doc': 'DOCTOR', - 'etu': 'STUDENT', - 'anc': 'FORMER STUDENT', - 'ens': 'TEACHER', - 'adm': 'ADMINISTRATIVE', - 'srv': 'SERVICE', - 'per': 'AGENT', - None: '', - } + "doc": "DOCTOR", + "etu": "STUDENT", + "anc": "FORMER STUDENT", + "ens": "TEACHER", + "adm": "ADMINISTRATIVE", + "srv": "SERVICE", + "per": "AGENT", + None: "", + } DEPARTMENTS = { - 'tc': 'TC', - 'gi': 'GI', - 'gesc': 'GESC', - 'na': 'NA', - 'mc': 'MC', - 'imap': 'IMAP', - 'huma': 'HUMA', - 'edim': 'EDIM', - 'ee': 'EE', - 'imsi': 'IMSI', - 'truc': 'NA', - None: 'NA', - } + "tc": "TC", + "gi": "GI", + "gesc": "GESC", + "na": "NA", + "mc": "MC", + "imap": "IMAP", + "huma": "HUMA", + "edim": "EDIM", + "ee": "EE", + "imsi": "IMSI", + "truc": "NA", + None: "NA", + } def get_random_free_email(): email = "no_email_%s@git.an" % random.randrange(4000, 40000) @@ -116,7 +142,8 @@ def migrate_core(): return email c = db.cursor(MySQLdb.cursors.SSDictCursor) - c.execute(""" + c.execute( + """ SELECT * FROM utilisateurs utl LEFT JOIN utl_etu ue @@ -128,36 +155,49 @@ def migrate_core(): LEFT JOIN loc_ville ville ON utl.id_ville = ville.id_ville -- WHERE utl.id_utilisateur = 9360 - """) + """ + ) User.objects.filter(id__gt=0).delete() print("Users deleted") for u in c: try: new = User( - id=u['id_utilisateur'], - last_name=to_unicode(u['nom_utl']) or "Bou", - first_name=to_unicode(u['prenom_utl']) or "Bi", - email=u['email_utl'], - second_email=u['email_utbm'] or "", - date_of_birth=u['date_naissance_utl'], - last_update=u['date_maj_utl'], - nick_name=to_unicode(u['surnom_utbm']), - sex=SEX[u['sexe_utl']], - tshirt_size=TSHIRT[u['taille_tshirt_utl']], - role=ROLE[u['role_utbm']], - department=DEPARTMENTS[u['departement_utbm']], - dpt_option=to_unicode(u['filiere_utbm']), - semester=u['semestre_utbm'] or 0, - quote=to_unicode(u['citation']), - school=to_unicode(u['nom_ecole_etudiant']), - promo=u['promo_utbm'] or 0, - forum_signature=to_unicode(u['signature_utl']), - address=(to_unicode(u['addresse_utl']) + ", " + to_unicode(u['cpostal_ville']) + " " + to_unicode(u['nom_ville'])), - parent_address=(to_unicode(u['adresse_parents']) + ", " + to_unicode(u['cpostal_parents']) + " " + to_unicode(u['ville_parents'])), - phone=u['tel_portable_utl'] or "", - parent_phone=u['tel_parents'] or "", - is_subscriber_viewable=bool(u['publique_utl']), + id=u["id_utilisateur"], + last_name=to_unicode(u["nom_utl"]) or "Bou", + first_name=to_unicode(u["prenom_utl"]) or "Bi", + email=u["email_utl"], + second_email=u["email_utbm"] or "", + date_of_birth=u["date_naissance_utl"], + last_update=u["date_maj_utl"], + nick_name=to_unicode(u["surnom_utbm"]), + sex=SEX[u["sexe_utl"]], + tshirt_size=TSHIRT[u["taille_tshirt_utl"]], + role=ROLE[u["role_utbm"]], + department=DEPARTMENTS[u["departement_utbm"]], + dpt_option=to_unicode(u["filiere_utbm"]), + semester=u["semestre_utbm"] or 0, + quote=to_unicode(u["citation"]), + school=to_unicode(u["nom_ecole_etudiant"]), + promo=u["promo_utbm"] or 0, + forum_signature=to_unicode(u["signature_utl"]), + address=( + to_unicode(u["addresse_utl"]) + + ", " + + to_unicode(u["cpostal_ville"]) + + " " + + to_unicode(u["nom_ville"]) + ), + parent_address=( + to_unicode(u["adresse_parents"]) + + ", " + + to_unicode(u["cpostal_parents"]) + + " " + + to_unicode(u["ville_parents"]) + ), + phone=u["tel_portable_utl"] or "", + parent_phone=u["tel_parents"] or "", + is_subscriber_viewable=bool(u["publique_utl"]), ) new.generate_username() new.set_password(str(random.randrange(1000000, 10000000))) @@ -168,12 +208,12 @@ def migrate_core(): new.save() print("New email generated") else: - print("FAIL for user %s: %s" % (u['id_utilisateur'], repr(e))) + print("FAIL for user %s: %s" % (u["id_utilisateur"], repr(e))) except Exception as e: - print("FAIL for user %s: %s" % (u['id_utilisateur'], repr(e))) + print("FAIL for user %s: %s" % (u["id_utilisateur"], repr(e))) c.close() print("Users migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_profile_pict(): PROFILE_ROOT = "/data/matmatronch/" @@ -182,16 +222,23 @@ def migrate_core(): profile.children.all().delete() print("Profiles pictures deleted") for filename in listdir(PROFILE_ROOT): - if filename.split('.')[-2] != "mini": + if filename.split(".")[-2] != "mini": try: - uid = filename.split('.')[0].split('-')[0] + uid = filename.split(".")[0].split("-")[0] user = User.objects.filter(id=int(uid)).first() if user: - f = File(open(PROFILE_ROOT + '/' + filename, 'rb')) - f.name = f.name.split('/')[-1] - t = filename.split('.')[1] - new_file = SithFile(parent=profile, name=filename, - file=f, owner=user, is_folder=False, mime_type="image/jpeg", size=f.size) + f = File(open(PROFILE_ROOT + "/" + filename, "rb")) + f.name = f.name.split("/")[-1] + t = filename.split(".")[1] + new_file = SithFile( + parent=profile, + name=filename, + file=f, + owner=user, + is_folder=False, + mime_type="image/jpeg", + size=f.size, + ) if t == "identity": new_file.save() user.profile_pict = new_file @@ -207,7 +254,7 @@ def migrate_core(): except Exception as e: print(repr(e)) print("Profile pictures migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) migrate_users() migrate_profile_pict() @@ -216,13 +263,15 @@ def migrate_core(): def migrate_club(): def migrate_clubs(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM asso asso WHERE nom_unix_asso <> "ae" AND nom_unix_asso <> "bdf" AND nom_unix_asso <> "laverie" - """) + """ + ) # club = cur.fetchone() # for k,v in club.items(): # print("%40s | %40s" % (k, v)) @@ -230,94 +279,89 @@ def migrate_club(): for c in cur: try: new = Club( - id=c['id_asso'], - name=to_unicode(c['nom_asso']), - unix_name=to_unicode(c['nom_unix_asso']), - address=to_unicode(c['adresse_postale']), - ) + id=c["id_asso"], + name=to_unicode(c["nom_asso"]), + unix_name=to_unicode(c["nom_unix_asso"]), + address=to_unicode(c["adresse_postale"]), + ) new.save() except Exception as e: - print("FAIL for club %s: %s" % (c['nom_unix_asso'], repr(e))) - cur.execute(""" + print("FAIL for club %s: %s" % (c["nom_unix_asso"], repr(e))) + cur.execute( + """ SELECT * FROM asso - """) + """ + ) for c in cur: - club = Club.objects.filter(id=c['id_asso']).first() - parent = Club.objects.filter(id=c['id_asso_parent']).first() + club = Club.objects.filter(id=c["id_asso"]).first() + parent = Club.objects.filter(id=c["id_asso_parent"]).first() club.parent = parent club.save() cur.close() print("Clubs migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_club_memberships(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM asso_membre - """) + """ + ) Membership.objects.all().delete() print("Memberships deleted") for m in cur: try: - club = Club.objects.filter(id=m['id_asso']).first() - user = User.objects.filter(id=m['id_utilisateur']).first() + club = Club.objects.filter(id=m["id_asso"]).first() + user = User.objects.filter(id=m["id_utilisateur"]).first() if club and user: new = Membership( - id=Membership.objects.count()+1, - club=club, - user=user, - start_date=m['date_debut'], - end_date=m['date_fin'], - role=m['role'], - description=to_unicode(m['desc_role']), - ) + id=Membership.objects.count() + 1, + club=club, + user=user, + start_date=m["date_debut"], + end_date=m["date_fin"], + role=m["role"], + description=to_unicode(m["desc_role"]), + ) new.save() except Exception as e: - print("FAIL for club membership %s: %s" % (m['id_asso'], repr(e))) + print("FAIL for club membership %s: %s" % (m["id_asso"], repr(e))) cur.close() print("Clubs memberships migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) # migrate_clubs() migrate_club_memberships() + def migrate_subscriptions(): - LOCATION = { - 5: "SEVENANS", - 6: "BELFORT", - 9: "MONTBELIARD", - None: "SEVENANS", - } + LOCATION = {5: "SEVENANS", 6: "BELFORT", 9: "MONTBELIARD", None: "SEVENANS"} TYPE = { - 0: 'un-semestre', - 1: 'deux-semestres', - 2: 'cursus-tronc-commun', - 3: 'cursus-branche', - 4: 'membre-honoraire', - 5: 'assidu', - 6: 'amicale/doceo', - 7: 'reseau-ut', - 8: 'crous', - 9: 'sbarro/esta', - 10: 'cursus-alternant', - None: 'un-semestre', - } - PAYMENT = { - 1: "CHECK", - 2: "CARD", - 3: "CASH", - 4: "OTHER", - 5: "EBOUTIC", - 0: "OTHER", - } + 0: "un-semestre", + 1: "deux-semestres", + 2: "cursus-tronc-commun", + 3: "cursus-branche", + 4: "membre-honoraire", + 5: "assidu", + 6: "amicale/doceo", + 7: "reseau-ut", + 8: "crous", + 9: "sbarro/esta", + 10: "cursus-alternant", + None: "un-semestre", + } + PAYMENT = {1: "CHECK", 2: "CARD", 3: "CASH", 4: "OTHER", 5: "EBOUTIC", 0: "OTHER"} cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM ae_cotisations - """) + """ + ) Subscription.objects.all().delete() print("Subscriptions deleted") @@ -325,70 +369,78 @@ def migrate_subscriptions(): print("Customers deleted") for r in cur: try: - user = User.objects.filter(id=r['id_utilisateur']).first() + user = User.objects.filter(id=r["id_utilisateur"]).first() if user: new = Subscription( - id=r['id_cotisation'], - member=user, - subscription_start=r['date_cotis'], - subscription_end=r['date_fin_cotis'], - subscription_type=TYPE[r['type_cotis']], - payment_method=PAYMENT[r['mode_paiement_cotis']], - location=LOCATION[r['id_comptoir']], - ) + id=r["id_cotisation"], + member=user, + subscription_start=r["date_cotis"], + subscription_end=r["date_fin_cotis"], + subscription_type=TYPE[r["type_cotis"]], + payment_method=PAYMENT[r["mode_paiement_cotis"]], + location=LOCATION[r["id_comptoir"]], + ) new.save() except Exception as e: - print("FAIL for subscription %s: %s" % (r['id_cotisation'], repr(e))) + print("FAIL for subscription %s: %s" % (r["id_cotisation"], repr(e))) cur.close() print("Subscriptions migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) + def migrate_counter(): def update_customer_account(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM ae_carte carte JOIN ae_cotisations cotis ON carte.id_cotisation = cotis.id_cotisation - """) + """ + ) for r in cur: try: - user = Customer.objects.filter(user_id=r['id_utilisateur']).first() + user = Customer.objects.filter(user_id=r["id_utilisateur"]).first() if user: - user.account_id = str(r['id_carte_ae']) + r['cle_carteae'].lower() + user.account_id = str(r["id_carte_ae"]) + r["cle_carteae"].lower() user.save() except Exception as e: - print("FAIL to update customer account for %s: %s" % (r['id_cotisation'], repr(e))) + print( + "FAIL to update customer account for %s: %s" + % (r["id_cotisation"], repr(e)) + ) cur.close() print("Customer accounts migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_counters(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_comptoir - """) + """ + ) Counter.objects.all().delete() for r in cur: try: - club = Club.objects.filter(id=r['id_assocpt']).first() + club = Club.objects.filter(id=r["id_assocpt"]).first() new = Counter( - id=r['id_comptoir'], - name=to_unicode(r['nom_cpt']), - club=club, - type="OFFICE", - ) + id=r["id_comptoir"], + name=to_unicode(r["nom_cpt"]), + club=club, + type="OFFICE", + ) new.save() except Exception as e: - print("FAIL to migrate counter %s: %s" % (r['id_comptoir'], repr(e))) + print("FAIL to migrate counter %s: %s" % (r["id_comptoir"], repr(e))) cur.close() eboutic = Counter.objects.filter(id=3).first() eboutic.type = "EBOUTIC" eboutic.save() print("Counters migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def reset_customer_amount(): Refilling.objects.all().delete() @@ -401,29 +453,27 @@ def migrate_counter(): def migrate_refillings(): BANK = { - 0: "OTHER", - 1: "SOCIETE-GENERALE", - 2: "BANQUE-POPULAIRE", - 3: "BNP", - 4: "CAISSE-EPARGNE", - 5: "CIC", - 6: "CREDIT-AGRICOLE", - 7: "CREDIT-MUTUEL", - 8: "CREDIT-LYONNAIS", - 9: "LA-POSTE", - 100: "OTHER", - None: "OTHER", - } - PAYMENT = { - 2: "CARD", - 1: "CASH", - 0: "CHECK", - } + 0: "OTHER", + 1: "SOCIETE-GENERALE", + 2: "BANQUE-POPULAIRE", + 3: "BNP", + 4: "CAISSE-EPARGNE", + 5: "CIC", + 6: "CREDIT-AGRICOLE", + 7: "CREDIT-MUTUEL", + 8: "CREDIT-LYONNAIS", + 9: "LA-POSTE", + 100: "OTHER", + None: "OTHER", + } + PAYMENT = {2: "CARD", 1: "CASH", 0: "CHECK"} cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_rechargements - """) + """ + ) root_cust = Customer.objects.filter(user__id=0).first() mde = Counter.objects.filter(id=1).first() Refilling.objects.all().delete() @@ -431,137 +481,159 @@ def migrate_counter(): fail = 100 for r in cur: try: - cust = Customer.objects.filter(user__id=r['id_utilisateur']).first() - user = User.objects.filter(id=r['id_utilisateur']).first() + cust = Customer.objects.filter(user__id=r["id_utilisateur"]).first() + user = User.objects.filter(id=r["id_utilisateur"]).first() if not cust: if not user: cust = root_cust else: - cust = Customer(user=user, amount=0, account_id=Customer.generate_account_id(fail)) + cust = Customer( + user=user, + amount=0, + account_id=Customer.generate_account_id(fail), + ) cust.save() fail += 1 - op = User.objects.filter(id=r['id_utilisateur_operateur']).first() - counter = Counter.objects.filter(id=r['id_comptoir']).first() + op = User.objects.filter(id=r["id_utilisateur_operateur"]).first() + counter = Counter.objects.filter(id=r["id_comptoir"]).first() new = Refilling( - id=r['id_rechargement'], - counter=counter or mde, - customer=cust or root_cust, - operator=op or root_cust.user, - amount=r['montant_rech']/100, - payment_method=PAYMENT[r['type_paiement_rech']], - bank=BANK[r['banque_rech']], - date=r['date_rech'].replace(tzinfo=timezone('Europe/Paris')), - ) + id=r["id_rechargement"], + counter=counter or mde, + customer=cust or root_cust, + operator=op or root_cust.user, + amount=r["montant_rech"] / 100, + payment_method=PAYMENT[r["type_paiement_rech"]], + bank=BANK[r["banque_rech"]], + date=r["date_rech"].replace(tzinfo=timezone("Europe/Paris")), + ) new.save() except Exception as e: - print("FAIL to migrate refilling %s for %s: %s" % (r['id_rechargement'], r['id_utilisateur'], repr(e))) + print( + "FAIL to migrate refilling %s for %s: %s" + % (r["id_rechargement"], r["id_utilisateur"], repr(e)) + ) cur.close() print("Refillings migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_typeproducts(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_type_produit - """) + """ + ) ProductType.objects.all().delete() print("Product types deleted") for r in cur: try: new = ProductType( - id=r['id_typeprod'], - name=to_unicode(r['nom_typeprod']), - description=to_unicode(r['description_typeprod']), - ) + id=r["id_typeprod"], + name=to_unicode(r["nom_typeprod"]), + description=to_unicode(r["description_typeprod"]), + ) new.save() except Exception as e: - print("FAIL to migrate product type %s: %s" % (r['nom_typeprod'], repr(e))) + print( + "FAIL to migrate product type %s: %s" % (r["nom_typeprod"], repr(e)) + ) cur.close() print("Product types migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_products(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_produits - """) + """ + ) Product.objects.all().delete() print("Product deleted") for r in cur: try: - type = ProductType.objects.filter(id=r['id_typeprod']).first() - club = Club.objects.filter(id=r['id_assocpt']).first() + type = ProductType.objects.filter(id=r["id_typeprod"]).first() + club = Club.objects.filter(id=r["id_assocpt"]).first() new = Product( - id=r['id_produit'], - product_type=type, - name=to_unicode(r['nom_prod']), - description=to_unicode(r['description_prod']), - code=to_unicode(r['cbarre_prod']), - purchase_price=r['prix_achat_prod']/100, - selling_price=r['prix_vente_prod']/100, - special_selling_price=r['prix_vente_barman_prod']/100, - club=club, - limit_age=r['mineur'] or 0, - tray=bool(r['plateau']), - ) + id=r["id_produit"], + product_type=type, + name=to_unicode(r["nom_prod"]), + description=to_unicode(r["description_prod"]), + code=to_unicode(r["cbarre_prod"]), + purchase_price=r["prix_achat_prod"] / 100, + selling_price=r["prix_vente_prod"] / 100, + special_selling_price=r["prix_vente_barman_prod"] / 100, + club=club, + limit_age=r["mineur"] or 0, + tray=bool(r["plateau"]), + ) new.save() except Exception as e: - print("FAIL to migrate product %s: %s" % (r['nom_prod'], repr(e))) + print("FAIL to migrate product %s: %s" % (r["nom_prod"], repr(e))) cur.close() print("Product migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_product_pict(): FILE_ROOT = "/data/files/" cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_produits WHERE id_file IS NOT NULL - """) + """ + ) for r in cur: try: - prod = Product.objects.filter(id=r['id_produit']).first() + prod = Product.objects.filter(id=r["id_produit"]).first() if prod: - f = File(open(FILE_ROOT + '/' + str(r['id_file']) + ".1", 'rb')) + f = File(open(FILE_ROOT + "/" + str(r["id_file"]) + ".1", "rb")) f.name = prod.name prod.icon = f prod.save() except Exception as e: print(repr(e)) print("Product pictures migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_products_to_counter(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_mise_en_vente - """) + """ + ) for r in cur: try: - product = Product.objects.filter(id=r['id_produit']).first() - counter = Counter.objects.filter(id=r['id_comptoir']).first() + product = Product.objects.filter(id=r["id_produit"]).first() + counter = Counter.objects.filter(id=r["id_comptoir"]).first() counter.products.add(product) counter.save() except Exception as e: - print("FAIL to set product %s in counter %s: %s" % (product, counter, repr(e))) + print( + "FAIL to set product %s in counter %s: %s" + % (product, counter, repr(e)) + ) cur.close() print("Product in counters migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_invoices(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_vendu ven LEFT JOIN cpt_debitfacture fac ON ven.id_facture = fac.id_facture WHERE fac.mode_paiement = 'SG' - """) + """ + ) Invoice.objects.all().delete() print("Invoices deleted") Refilling.objects.filter(payment_method="CARD").delete() @@ -571,21 +643,29 @@ def migrate_counter(): root = User.objects.filter(id=0).first() for r in cur: try: - product = Product.objects.filter(id=r['id_produit']).first() - user = User.objects.filter(id=r['id_utilisateur_client']).first() - i = Invoice.objects.filter(id=r['id_facture']).first() or Invoice(id=r['id_facture']) + product = Product.objects.filter(id=r["id_produit"]).first() + user = User.objects.filter(id=r["id_utilisateur_client"]).first() + i = Invoice.objects.filter(id=r["id_facture"]).first() or Invoice( + id=r["id_facture"] + ) i.user = user or root for f in i._meta.local_fields: if f.name == "date": f.auto_now = False - i.date = r['date_facture'].replace(tzinfo=timezone('Europe/Paris')) + i.date = r["date_facture"].replace(tzinfo=timezone("Europe/Paris")) i.save() - InvoiceItem(invoice=i, product_id=product.id, product_name=product.name, type_id=product.product_type.id, - product_unit_price=r['prix_unit']/100, quantity=r['quantite']).save() + InvoiceItem( + invoice=i, + product_id=product.id, + product_name=product.name, + type_id=product.product_type.id, + product_unit_price=r["prix_unit"] / 100, + quantity=r["quantite"], + ).save() except ValidationError as e: print(repr(e) + " for %s (%s)" % (customer, customer.user.id)) except Exception as e: - print("FAIL to migrate invoice %s: %s" % (r['id_facture'], repr(e))) + print("FAIL to migrate invoice %s: %s" % (r["id_facture"], repr(e))) cur.close() for i in Invoice.objects.all(): for f in i._meta.local_fields: @@ -593,17 +673,19 @@ def migrate_counter(): f.auto_now = False i.validate() print("Invoices migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_sellings(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_vendu ven LEFT JOIN cpt_debitfacture fac ON ven.id_facture = fac.id_facture WHERE fac.mode_paiement = 'AE' - """) + """ + ) Selling.objects.filter(payment_method="SITH_ACCOUNT").delete() print("Sith account selling deleted") for c in Customer.objects.all(): @@ -616,57 +698,62 @@ def migrate_counter(): beer = Product.objects.filter(id=1).first() for r in cur: try: - product = Product.objects.filter(id=r['id_produit']).first() or beer - club = Club.objects.filter(id=r['id_assocpt']).first() or ae - counter = Counter.objects.filter(id=r['id_comptoir']).first() or mde - op = User.objects.filter(id=r['id_utilisateur']).first() or root - customer = Customer.objects.filter(user__id=r['id_utilisateur_client']).first() or root.customer + product = Product.objects.filter(id=r["id_produit"]).first() or beer + club = Club.objects.filter(id=r["id_assocpt"]).first() or ae + counter = Counter.objects.filter(id=r["id_comptoir"]).first() or mde + op = User.objects.filter(id=r["id_utilisateur"]).first() or root + customer = ( + Customer.objects.filter(user__id=r["id_utilisateur_client"]).first() + or root.customer + ) new = Selling( - label=product.name or "Produit inexistant", - counter=counter, - club=club, - product=product, - seller=op, - customer=customer, - unit_price=r['prix_unit']/100, - quantity=r['quantite'], - payment_method="SITH_ACCOUNT", - date=r['date_facture'].replace(tzinfo=timezone('Europe/Paris')), - ) + label=product.name or "Produit inexistant", + counter=counter, + club=club, + product=product, + seller=op, + customer=customer, + unit_price=r["prix_unit"] / 100, + quantity=r["quantite"], + payment_method="SITH_ACCOUNT", + date=r["date_facture"].replace(tzinfo=timezone("Europe/Paris")), + ) new.save() except ValidationError as e: print(repr(e) + " for %s (%s)" % (customer, customer.user.id)) except Exception as e: - print("FAIL to migrate selling %s: %s" % (r['id_facture'], repr(e))) + print("FAIL to migrate selling %s: %s" % (r["id_facture"], repr(e))) cur.close() print("Sellings migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_permanencies(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_tracking - """) + """ + ) Permanency.objects.all().delete() print("Permanencies deleted") for r in cur: try: - counter = Counter.objects.filter(id=r['id_comptoir']).first() - user = User.objects.filter(id=r['id_utilisateur']).first() + counter = Counter.objects.filter(id=r["id_comptoir"]).first() + user = User.objects.filter(id=r["id_utilisateur"]).first() new = Permanency( - user=user, - counter=counter, - start=r['logged_time'].replace(tzinfo=timezone('Europe/Paris')), - activity=r['logged_time'].replace(tzinfo=timezone('Europe/Paris')), - end=r['closed_time'].replace(tzinfo=timezone('Europe/Paris')), - ) + user=user, + counter=counter, + start=r["logged_time"].replace(tzinfo=timezone("Europe/Paris")), + activity=r["logged_time"].replace(tzinfo=timezone("Europe/Paris")), + end=r["closed_time"].replace(tzinfo=timezone("Europe/Paris")), + ) new.save() except Exception as e: print("FAIL to migrate permanency: %s" % (repr(e))) cur.close() print("Permanencies migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) update_customer_account() migrate_counters() @@ -680,241 +767,252 @@ def migrate_counter(): migrate_refillings() migrate_sellings() + def check_accounts(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM utilisateurs - """) + """ + ) mde = Counter.objects.filter(id=1).first() - ae = Club.objects.filter(unix_name='ae').first() + ae = Club.objects.filter(unix_name="ae").first() root = User.objects.filter(id=0).first() for r in cur: - if r['montant_compte'] and r['montant_compte'] > 0: + if r["montant_compte"] and r["montant_compte"] > 0: try: - cust = Customer.objects.filter(user__id=r['id_utilisateur']).first() - if int(cust.amount * 100) != r['montant_compte']: - print("Adding %s to %s's account" % (float(cust.amount) - (r['montant_compte']/100), cust.user)) + cust = Customer.objects.filter(user__id=r["id_utilisateur"]).first() + if int(cust.amount * 100) != r["montant_compte"]: + print( + "Adding %s to %s's account" + % (float(cust.amount) - (r["montant_compte"] / 100), cust.user) + ) new = Selling( - label="Ajustement migration base de donnée", - counter=mde, - club=ae, - product=None, - seller=root, - customer=cust, - unit_price=float(cust.amount) - (r['montant_compte']/100.), - quantity=1, - payment_method="SITH_ACCOUNT", - ) + label="Ajustement migration base de donnée", + counter=mde, + club=ae, + product=None, + seller=root, + customer=cust, + unit_price=float(cust.amount) - (r["montant_compte"] / 100.0), + quantity=1, + payment_method="SITH_ACCOUNT", + ) new.save() except Exception as e: print("FAIL to adjust user account: %s" % (repr(e))) + + ### Accounting + def migrate_accounting(): def migrate_companies(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM entreprise - """) + """ + ) Company.objects.all().delete() print("Company deleted") for r in cur: try: new = Company( - id=r['id_ent'], - name=to_unicode(r['nom_entreprise']), - street=to_unicode(r['rue_entreprise']), - city=to_unicode(r['ville_entreprise']), - postcode=to_unicode(r['cpostal_entreprise']), - country=to_unicode(r['pays_entreprise']), - phone=to_unicode(r['telephone_entreprise']), - email=to_unicode(r['email_entreprise']), - website=to_unicode(r['siteweb_entreprise']), - ) + id=r["id_ent"], + name=to_unicode(r["nom_entreprise"]), + street=to_unicode(r["rue_entreprise"]), + city=to_unicode(r["ville_entreprise"]), + postcode=to_unicode(r["cpostal_entreprise"]), + country=to_unicode(r["pays_entreprise"]), + phone=to_unicode(r["telephone_entreprise"]), + email=to_unicode(r["email_entreprise"]), + website=to_unicode(r["siteweb_entreprise"]), + ) new.save() except Exception as e: print("FAIL to migrate company: %s" % (repr(e))) cur.close() print("Companies migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_bank_accounts(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_cpbancaire - """) + """ + ) BankAccount.objects.all().delete() print("Bank accounts deleted") - ae = Club.objects.filter(unix_name='ae').first() + ae = Club.objects.filter(unix_name="ae").first() for r in cur: try: new = BankAccount( - id=r['id_cptbc'], - club=ae, - name=to_unicode(r['nom_cptbc']), - ) + id=r["id_cptbc"], club=ae, name=to_unicode(r["nom_cptbc"]) + ) new.save() except Exception as e: print("FAIL to migrate bank account: %s" % (repr(e))) cur.close() print("Bank accounts migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_club_accounts(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_cpasso - """) + """ + ) ClubAccount.objects.all().delete() print("Club accounts deleted") ae = Club.objects.filter(id=1).first() for r in cur: try: - club = Club.objects.filter(id=r['id_asso']).first() or ae - bank_acc = BankAccount.objects.filter(id=r['id_cptbc']).first() + club = Club.objects.filter(id=r["id_asso"]).first() or ae + bank_acc = BankAccount.objects.filter(id=r["id_cptbc"]).first() new = ClubAccount( - id=r['id_cptasso'], - club=club, - name=club.name[:30], - bank_account=bank_acc, - ) + id=r["id_cptasso"], + club=club, + name=club.name[:30], + bank_account=bank_acc, + ) new.save() except Exception as e: print("FAIL to migrate club account: %s" % (repr(e))) cur.close() print("Club accounts migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_journals(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_classeur - """) + """ + ) GeneralJournal.objects.all().delete() print("General journals deleted") for r in cur: try: - club_acc = ClubAccount.objects.filter(id=r['id_cptasso']).first() + club_acc = ClubAccount.objects.filter(id=r["id_cptasso"]).first() new = GeneralJournal( - id=r['id_classeur'], - club_account=club_acc, - name=to_unicode(r['nom_classeur']), - start_date=r['date_debut_classeur'], - end_date=r['date_fin_classeur'], - closed=bool(r['ferme']), - ) + id=r["id_classeur"], + club_account=club_acc, + name=to_unicode(r["nom_classeur"]), + start_date=r["date_debut_classeur"], + end_date=r["date_fin_classeur"], + closed=bool(r["ferme"]), + ) new.save() except Exception as e: print("FAIL to migrate general journal: %s" % (repr(e))) cur.close() print("General journals migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_accounting_types(): - MOVEMENT = { - -1: "DEBIT", - 0: "NEUTRAL", - 1: "CREDIT", - } + MOVEMENT = {-1: "DEBIT", 0: "NEUTRAL", 1: "CREDIT"} cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_op_plcptl - """) + """ + ) AccountingType.objects.all().delete() print("Accounting types deleted") for r in cur: try: new = AccountingType( - id=r['id_opstd'], - code=str(r['code_plan']), - label=to_unicode(r['libelle_plan']).capitalize(), - movement_type=MOVEMENT[r['type_mouvement']], - ) + id=r["id_opstd"], + code=str(r["code_plan"]), + label=to_unicode(r["libelle_plan"]).capitalize(), + movement_type=MOVEMENT[r["type_mouvement"]], + ) new.save() except Exception as e: print("FAIL to migrate accounting type: %s" % (repr(e))) cur.close() print("Accounting types migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_simpleaccounting_types(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_op_clb WHERE id_asso IS NULL - """) + """ + ) SimplifiedAccountingType.objects.all().delete() print("Simple accounting types deleted") for r in cur: try: - at = AccountingType.objects.filter(id=r['id_opstd']).first() + at = AccountingType.objects.filter(id=r["id_opstd"]).first() new = SimplifiedAccountingType( - id=r['id_opclb'], - label=to_unicode(r['libelle_opclb']).capitalize(), - accounting_type=at, - ) + id=r["id_opclb"], + label=to_unicode(r["libelle_opclb"]).capitalize(), + accounting_type=at, + ) new.save() except Exception as e: print("FAIL to migrate simple type: %s" % (repr(e))) cur.close() print("Simple accounting types migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_labels(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_libelle WHERE id_asso IS NOT NULL - """) + """ + ) Label.objects.all().delete() print("Labels deleted") for r in cur: try: - club_accounts = ClubAccount.objects.filter(club__id=r['id_asso']).all() + club_accounts = ClubAccount.objects.filter(club__id=r["id_asso"]).all() for ca in club_accounts: - new = Label( - club_account=ca, - name=to_unicode(r['nom_libelle']), - ) + new = Label(club_account=ca, name=to_unicode(r["nom_libelle"])) new.save() except Exception as e: print("FAIL to migrate label: %s" % (repr(e))) cur.close() print("Labels migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_operations(): MODE = { - 1: "CHECK", - 2: "CASH", - 3: "TRANSFERT", - 4: "CARD", - 0: "CASH", - None: "CASH", - } - MOVEMENT_TYPE = { - -1: "DEBIT", - 0: "NEUTRAL", - 1: "CREDIT", - None: "NEUTRAL", - } + 1: "CHECK", + 2: "CASH", + 3: "TRANSFERT", + 4: "CARD", + 0: "CASH", + None: "CASH", + } + MOVEMENT_TYPE = {-1: "DEBIT", 0: "NEUTRAL", 1: "CREDIT", None: "NEUTRAL"} cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_operation op LEFT JOIN cpta_op_clb clb ON op.id_opclb = clb.id_opclb LEFT JOIN cpta_libelle lab ON op.id_libelle = lab.id_libelle - """) + """ + ) Operation.objects.all().delete() print("Operation deleted") for r in cur: @@ -922,44 +1020,63 @@ def migrate_accounting(): simple_type = None accounting_type = None label = None - if r['id_opclb']: - simple_type = SimplifiedAccountingType.objects.filter(id=r['id_opclb']).first() - if r['id_opstd']: - accounting_type = AccountingType.objects.filter(id=r['id_opstd']).first() + if r["id_opclb"]: + simple_type = SimplifiedAccountingType.objects.filter( + id=r["id_opclb"] + ).first() + if r["id_opstd"]: + accounting_type = AccountingType.objects.filter( + id=r["id_opstd"] + ).first() if not accounting_type and simple_type: accounting_type = simple_type.accounting_type if not accounting_type: - accounting_type = AccountingType.objects.filter(movement_type=MOVEMENT_TYPE[r['type_mouvement']]).first() - journal = GeneralJournal.objects.filter(id=r['id_classeur']).first() - if r['id_libelle']: - label = journal.club_account.labels.filter(name=to_unicode(r['nom_libelle'])).first() + accounting_type = AccountingType.objects.filter( + movement_type=MOVEMENT_TYPE[r["type_mouvement"]] + ).first() + journal = GeneralJournal.objects.filter(id=r["id_classeur"]).first() + if r["id_libelle"]: + label = journal.club_account.labels.filter( + name=to_unicode(r["nom_libelle"]) + ).first() + def get_target_type(): - if r['id_utilisateur']: + if r["id_utilisateur"]: return "USER" - if r['id_asso']: + if r["id_asso"]: return "CLUB" - if r['id_ent']: + if r["id_ent"]: return "COMPANY" - if r['id_classeur']: + if r["id_classeur"]: return "ACCOUNT" + def get_target_id(): - return int(r['id_utilisateur'] or r['id_asso'] or r['id_ent'] or r['id_classeur']) or None - new = Operation( - id=r['id_op'], - journal=journal, - amount=r['montant_op']/100, - date=r['date_op'] or journal.end_date, - remark=to_unicode(r['commentaire_op']), - mode=MODE[r['mode_op']], - cheque_number=str(r['num_cheque_op']), - done=bool(r['op_effctue']), - simpleaccounting_type=simple_type, - accounting_type=accounting_type, - target_type=get_target_type(), - target_id=get_target_id(), - target_label="-", - label=label, + return ( + int( + r["id_utilisateur"] + or r["id_asso"] + or r["id_ent"] + or r["id_classeur"] ) + or None + ) + + new = Operation( + id=r["id_op"], + journal=journal, + amount=r["montant_op"] / 100, + date=r["date_op"] or journal.end_date, + remark=to_unicode(r["commentaire_op"]), + mode=MODE[r["mode_op"]], + cheque_number=str(r["num_cheque_op"]), + done=bool(r["op_effctue"]), + simpleaccounting_type=simple_type, + accounting_type=accounting_type, + target_type=get_target_type(), + target_id=get_target_id(), + target_label="-", + label=label, + ) try: new.clean() except: @@ -970,19 +1087,21 @@ def migrate_accounting(): print("FAIL to migrate operation: %s" % (repr(e))) cur.close() print("Operations migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def make_operation_links(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_operation - """) + """ + ) for r in cur: - if r['id_op_liee']: + if r["id_op_liee"]: try: - op1 = Operation.objects.filter(id=r['id_op']).first() - op2 = Operation.objects.filter(id=r['id_op_liee']).first() + op1 = Operation.objects.filter(id=r["id_op"]).first() + op2 = Operation.objects.filter(id=r["id_op_liee"]).first() op1.linked_operation = op2 op1.save() op2.linked_operation = op1 @@ -991,7 +1110,7 @@ def migrate_accounting(): print("FAIL to link operations: %s" % (repr(e))) cur.close() print("Operations links migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) migrate_companies() migrate_accounting_types() @@ -1003,54 +1122,58 @@ def migrate_accounting(): migrate_operations() make_operation_links() + def migrate_godfathers(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM parrains - """) + """ + ) for r in cur: try: - father = User.objects.filter(id=r['id_utilisateur']).first() - child = User.objects.filter(id=r['id_utilisateur_fillot']).first() + father = User.objects.filter(id=r["id_utilisateur"]).first() + child = User.objects.filter(id=r["id_utilisateur_fillot"]).first() father.godchildren.add(child) father.save() except Exception as e: print("FAIL to migrate godfathering: %s" % (repr(e))) cur.close() print("Godfathers migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) + def migrate_etickets(): FILE_ROOT = "/data/files/" cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_etickets - """) + """ + ) Eticket.objects.all().delete() print("Etickets deleted") for r in cur: try: - p = Product.objects.filter(id=r['id_produit']).first() + p = Product.objects.filter(id=r["id_produit"]).first() try: - f = File(open(FILE_ROOT + '/' + str(r['banner']) + ".1", 'rb')) + f = File(open(FILE_ROOT + "/" + str(r["banner"]) + ".1", "rb")) except: f = None e = Eticket( - product=p, - secret=to_unicode(r['secret']), - banner=f, - event_title=p.name, - ) + product=p, secret=to_unicode(r["secret"]), banner=f, event_title=p.name + ) e.save() - e.secret=to_unicode(r['secret']) + e.secret = to_unicode(r["secret"]) e.save() except Exception as e: print("FAIL to migrate eticket: %s" % (repr(e))) cur.close() print("Etickets migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) + def migrate_sas(): album_link = {} @@ -1058,34 +1181,44 @@ def migrate_sas(): FILE_ROOT = "/data/sas/" SithFile.objects.filter(id__gte=18892).delete() print("Album/Pictures deleted") - reset_index('core', 'sas') + reset_index("core", "sas") cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM sas_cat_photos - """) + """ + ) root = User.objects.filter(username="root").first() for r in cur: try: - a = Album(name=to_unicode(r['nom_catph']), owner=root, is_moderated=True, parent=None) + a = Album( + name=to_unicode(r["nom_catph"]), + owner=root, + is_moderated=True, + parent=None, + ) a.save() - album_link[str(r['id_catph'])] = a.id + album_link[str(r["id_catph"])] = a.id except Exception as e: print("FAIL to migrate Album: %s" % (repr(e))) print("Album moved, need to make the tree") - cur.execute(""" + cur.execute( + """ SELECT * FROM sas_cat_photos - """) + """ + ) for r in cur: try: - p = Album.objects.filter(id=album_link[str(r['id_catph_parent'])]).first() - a = Album.objects.filter(id=album_link[str(r['id_catph'])]).first() + p = Album.objects.filter(id=album_link[str(r["id_catph_parent"])]).first() + a = Album.objects.filter(id=album_link[str(r["id_catph"])]).first() a.parent = p a.save() - except: pass + except: + pass print("Album migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) with open("albums.link", "w") as f: f.write(str(album_link)) cur.close() @@ -1093,41 +1226,46 @@ def migrate_sas(): chunk = 0 while not finished: cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM sas_photos ORDER BY 'id_photo' LIMIT %s, 1000 - """, (chunk*1000, )) + """, + (chunk * 1000,), + ) has_result = False for r in cur: try: - user = User.objects.filter(id=r['id_utilisateur']).first() or root - parent = Album.objects.filter(id=album_link[str(r['id_catph'])]).first() + user = User.objects.filter(id=r["id_utilisateur"]).first() or root + parent = Album.objects.filter(id=album_link[str(r["id_catph"])]).first() file_name = FILE_ROOT - if r['date_prise_vue']: - file_name += r['date_prise_vue'].strftime("%Y/%m/%d") + if r["date_prise_vue"]: + file_name += r["date_prise_vue"].strftime("%Y/%m/%d") else: - file_name += '/'.join(["1970", "01", "01"]) - file_name += "/" + str(r['id_photo']) + ".jpg" + file_name += "/".join(["1970", "01", "01"]) + file_name += "/" + str(r["id_photo"]) + ".jpg" file = File(open(file_name, "rb")) - file.name = str(r['id_photo']) + ".jpg" + file.name = str(r["id_photo"]) + ".jpg" p = Picture( - name=str(r['id_photo']) + ".jpg", - owner=user, - is_moderated=True, - is_folder=False, - mime_type="image/jpeg", - parent=parent, - file=file, - ) - if r['date_prise_vue']: - p.date = r['date_prise_vue'].replace(tzinfo=timezone('Europe/Paris')) + name=str(r["id_photo"]) + ".jpg", + owner=user, + is_moderated=True, + is_folder=False, + mime_type="image/jpeg", + parent=parent, + file=file, + ) + if r["date_prise_vue"]: + p.date = r["date_prise_vue"].replace( + tzinfo=timezone("Europe/Paris") + ) else: - p.date = r['date_ajout_ph'].replace(tzinfo=timezone('Europe/Paris')) + p.date = r["date_ajout_ph"].replace(tzinfo=timezone("Europe/Paris")) for f in p._meta.local_fields: if f.name == "date": f.auto_now = False @@ -1135,202 +1273,228 @@ def migrate_sas(): p.save() db2 = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) cur2 = db2.cursor(MySQLdb.cursors.SSDictCursor) - cur2.execute(""" + cur2.execute( + """ SELECT * FROM sas_personnes_photos WHERE id_photo = %s - """, (r['id_photo'], )) + """, + (r["id_photo"],), + ) for r2 in cur2: try: - u = User.objects.filter(id=r2['id_utilisateur']).first() + u = User.objects.filter(id=r2["id_utilisateur"]).first() if u: PeoplePictureRelation(user=u, picture=p).save() except: - print("Fail to associate user %d to picture %d" % (r2['id_utilisateur'], p.id)) + print( + "Fail to associate user %d to picture %d" + % (r2["id_utilisateur"], p.id) + ) has_result = True except Exception as e: print("FAIL to migrate Picture: %s" % (repr(e))) cur.close() print("Chunk %d migrated at %s" % (chunk, str(datetime.datetime.now()))) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) chunk += 1 finished = not has_result print("SAS migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) # try: # f = File(open(FILE_ROOT + '/' + str(r['banner']) + ".1", 'rb')) # except: # f = None + def reset_sas_moderators(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM sas_photos WHERE id_utilisateur_moderateur IS NOT NULL - """) + """ + ) for r in cur: try: - name = str(r['id_photo']) + '.jpg' + name = str(r["id_photo"]) + ".jpg" pict = SithFile.objects.filter(name__icontains=name, is_in_sas=True).first() - user = User.objects.filter(id=r['id_utilisateur_moderateur']).first() + user = User.objects.filter(id=r["id_utilisateur_moderateur"]).first() if pict and user: pict.moderator = user pict.save() else: - print("No pict %s (%s) or user %s (%s)" %(pict, name, user, r['id_utilisateur_moderateur'])) + print( + "No pict %s (%s) or user %s (%s)" + % (pict, name, user, r["id_utilisateur_moderateur"]) + ) except Exception as e: print(repr(e)) + def migrate_forum(): print("Migrating forum") + def migrate_forums(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) print(" Cleaning up forums") Forum.objects.all().delete() - cur.execute(""" + cur.execute( + """ SELECT * FROM frm_forum WHERE id_forum <> 1 - """) + """ + ) print(" Migrating forums") for r in cur: try: # parent = Forum.objects.filter(id=r['id_forum_parent']).first() - club = Club.objects.filter(id=r['id_asso']).first() + club = Club.objects.filter(id=r["id_asso"]).first() ae = Club.objects.filter(id=settings.SITH_MAIN_CLUB_ID).first() forum = Forum( - id=r['id_forum'], - name=to_unicode(r['titre_forum']), - description=to_unicode(r['description_forum'])[:511], - is_category=bool(r['categorie_forum']), + id=r["id_forum"], + name=to_unicode(r["titre_forum"]), + description=to_unicode(r["description_forum"])[:511], + is_category=bool(r["categorie_forum"]), # parent=parent, owner_club=club or ae, - number=r['ordre_forum'], - ) + number=r["ordre_forum"], + ) forum.save() except Exception as e: print(" FAIL to migrate forum: %s" % (repr(e))) - cur.execute(""" + cur.execute( + """ SELECT * FROM frm_forum WHERE id_forum_parent <> 1 - """) + """ + ) for r in cur: - parent = Forum.objects.filter(id=r['id_forum_parent']).first() - forum = Forum.objects.filter(id=r['id_forum']).first() + parent = Forum.objects.filter(id=r["id_forum_parent"]).first() + forum = Forum.objects.filter(id=r["id_forum"]).first() forum.parent = parent forum.save() cur.close() print(" Forums migrated at %s" % datetime.datetime.now()) - print(" Running time: %s" % (datetime.datetime.now()-start)) + print(" Running time: %s" % (datetime.datetime.now() - start)) def migrate_topics(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) print(" Cleaning up topics") ForumTopic.objects.all().delete() - cur.execute(""" + cur.execute( + """ SELECT * FROM frm_sujet - """) + """ + ) print(" Migrating topics") for r in cur: try: - parent = Forum.objects.filter(id=r['id_forum']).first() + parent = Forum.objects.filter(id=r["id_forum"]).first() saloon = Forum.objects.filter(id=3).first() - author = User.objects.filter(id=r['id_utilisateur']).first() + author = User.objects.filter(id=r["id_utilisateur"]).first() root = User.objects.filter(id=0).first() topic = ForumTopic( - id=r['id_sujet'], + id=r["id_sujet"], author=author or root, forum=parent or saloon, - _title=to_unicode(r['titre_sujet'])[:64], - description=to_unicode(r['soustitre_sujet']), - ) + _title=to_unicode(r["titre_sujet"])[:64], + description=to_unicode(r["soustitre_sujet"]), + ) topic.save() except Exception as e: print(" FAIL to migrate topic: %s" % (repr(e))) cur.close() print(" Topics migrated at %s" % datetime.datetime.now()) - print(" Running time: %s" % (datetime.datetime.now()-start)) + print(" Running time: %s" % (datetime.datetime.now() - start)) def migrate_messages(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) print(" Cleaning up messages") ForumMessage.objects.all().delete() - cur.execute(""" + cur.execute( + """ SELECT * FROM frm_message - """) + """ + ) print(" Migrating messages") for r in cur: try: - topic = ForumTopic.objects.filter(id=r['id_sujet']).first() - author = User.objects.filter(id=r['id_utilisateur']).first() + topic = ForumTopic.objects.filter(id=r["id_sujet"]).first() + author = User.objects.filter(id=r["id_utilisateur"]).first() root = User.objects.filter(id=0).first() msg = ForumMessage( - id=r['id_message'], + id=r["id_message"], topic=topic, author=author or root, - title=to_unicode(r['titre_message'])[:63], - date=r['date_message'].replace(tzinfo=timezone('Europe/Paris')), - ) + title=to_unicode(r["titre_message"])[:63], + date=r["date_message"].replace(tzinfo=timezone("Europe/Paris")), + ) try: - if r['syntaxengine_message'] == "doku": - msg.message = doku_to_markdown(to_unicode(r['contenu_message'])) + if r["syntaxengine_message"] == "doku": + msg.message = doku_to_markdown(to_unicode(r["contenu_message"])) else: - msg.message = bbcode_to_markdown(to_unicode(r['contenu_message'])) + msg.message = bbcode_to_markdown( + to_unicode(r["contenu_message"]) + ) except: - msg.message = to_unicode(r['contenu_message']) + msg.message = to_unicode(r["contenu_message"]) msg.save() except Exception as e: print(" FAIL to migrate message: %s" % (repr(e))) cur.close() print(" Messages migrated at %s" % datetime.datetime.now()) - print(" Running time: %s" % (datetime.datetime.now()-start)) + print(" Running time: %s" % (datetime.datetime.now() - start)) def migrate_message_infos(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) print(" Cleaning up message meta") ForumMessageMeta.objects.all().delete() - cur.execute(""" + cur.execute( + """ SELECT * FROM frm_modere_info - """) + """ + ) print(" Migrating message meta") ACTIONS = { - "EDIT": "EDIT", - "AUTOEDIT": "EDIT", - "UNDELETE": "UNDELETE", - "DELETE": "DELETE", - "DELETEFIRST": "DELETE", - "AUTODELETE": "DELETE", - } + "EDIT": "EDIT", + "AUTOEDIT": "EDIT", + "UNDELETE": "UNDELETE", + "DELETE": "DELETE", + "DELETEFIRST": "DELETE", + "AUTODELETE": "DELETE", + } for r in cur: try: - msg = ForumMessage.objects.filter(id=r['id_message']).first() - author = User.objects.filter(id=r['id_utilisateur']).first() + msg = ForumMessage.objects.filter(id=r["id_message"]).first() + author = User.objects.filter(id=r["id_utilisateur"]).first() root = User.objects.filter(id=0).first() meta = ForumMessageMeta( message=msg, user=author or root, - date=r['modere_date'].replace(tzinfo=timezone('Europe/Paris')), - action=ACTIONS[r['modere_action']], - ) + date=r["modere_date"].replace(tzinfo=timezone("Europe/Paris")), + action=ACTIONS[r["modere_action"]], + ) meta.save() except Exception as e: print(" FAIL to migrate message meta: %s" % (repr(e))) cur.close() print(" Messages meta migrated at %s" % datetime.datetime.now()) - print(" Running time: %s" % (datetime.datetime.now()-start)) + print(" Running time: %s" % (datetime.datetime.now() - start)) migrate_forums() migrate_topics() migrate_messages() migrate_message_infos() print("Forum migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_mailings(): @@ -1342,51 +1506,66 @@ def migrate_mailings(): print("Migrating old mailing database") - cur.execute(""" + cur.execute( + """ SELECT * FROM mailing - """) + """ + ) moderator = User.objects.get(id=0) for mailing in cur: - club = Club.objects.filter(id=mailing['id_asso_parent']) + club = Club.objects.filter(id=mailing["id_asso_parent"]) if club.exists(): print(mailing) club = club.first() - if mailing['nom']: - mailing['nom'] = '.' + mailing['nom'] - Mailing(id=mailing['id_mailing'], club=club, email=to_unicode(club.unix_name + mailing['nom']), - moderator=moderator, is_moderated=(mailing['is_valid'] > 0)).save() + if mailing["nom"]: + mailing["nom"] = "." + mailing["nom"] + Mailing( + id=mailing["id_mailing"], + club=club, + email=to_unicode(club.unix_name + mailing["nom"]), + moderator=moderator, + is_moderated=(mailing["is_valid"] > 0), + ).save() print("-------------------") - cur.execute(""" + cur.execute( + """ SELECT * FROM mailing_membres - """) + """ + ) for mailing_sub in cur: - mailing = Mailing.objects.filter(id=mailing_sub['id_mailing']) + mailing = Mailing.objects.filter(id=mailing_sub["id_mailing"]) if mailing.exists(): print(mailing_sub) mailing = mailing.first() - if mailing_sub['id_user'] and User.objects.filter(id=mailing_sub['id_user']).exists(): - user = User.objects.get(id=mailing_sub['id_user']) + if ( + mailing_sub["id_user"] + and User.objects.filter(id=mailing_sub["id_user"]).exists() + ): + user = User.objects.get(id=mailing_sub["id_user"]) MailingSubscription(mailing=mailing, user=user, email=user.email).save() - elif mailing_sub['email']: - MailingSubscription(mailing=mailing, email=to_unicode(mailing_sub['email'])).save() + elif mailing_sub["email"]: + MailingSubscription( + mailing=mailing, email=to_unicode(mailing_sub["email"]) + ).save() def migrate_club_again(): - cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute("SELECT * FROM asso") + cur = db.cursor(MySQLdb.cursors.SSDictCursor) + cur.execute("SELECT * FROM asso") - print("Migrating club is_active") + print("Migrating club is_active") - for club in cur: - try: - c = Club.objects.get(unix_name=club['nom_unix_asso']) - c.is_active = club['hidden'] == 0 - c.save() - except: pass + for club in cur: + try: + c = Club.objects.get(unix_name=club["nom_unix_asso"]) + c.is_active = club["hidden"] == 0 + c.save() + except: + pass def main(): diff --git a/rootplace/__init__.py b/rootplace/__init__.py index 0a9419f8..0ace29c4 100644 --- a/rootplace/__init__.py +++ b/rootplace/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/rootplace/urls.py b/rootplace/urls.py index 6ab433e3..b94b4030 100644 --- a/rootplace/urls.py +++ b/rootplace/urls.py @@ -26,6 +26,4 @@ from django.conf.urls import url from rootplace.views import * -urlpatterns = [ - url(r'^merge$', MergeUsersView.as_view(), name='merge'), -] +urlpatterns = [url(r"^merge$", MergeUsersView.as_view(), name="merge")] diff --git a/rootplace/views.py b/rootplace/views.py index a03ab3ae..ed9c2773 100644 --- a/rootplace/views.py +++ b/rootplace/views.py @@ -59,7 +59,7 @@ def merge_users(u1, u2): u1.godfathers.add(u) u1.save() for i in u2.invoices.all(): - for f in i._meta.local_fields: # I have sadly not found anything better :/ + for f in i._meta.local_fields: # I have sadly not found anything better :/ if f.name == "date": f.auto_now = False u1.invoices.add(i) @@ -88,8 +88,12 @@ def merge_users(u1, u2): class MergeForm(forms.Form): - user1 = AutoCompleteSelectField('users', label=_("User that will be kept"), help_text=None, required=True) - user2 = AutoCompleteSelectField('users', label=_("User that will be deleted"), help_text=None, required=True) + user1 = AutoCompleteSelectField( + "users", label=_("User that will be kept"), help_text=None, required=True + ) + user2 = AutoCompleteSelectField( + "users", label=_("User that will be deleted"), help_text=None, required=True + ) class MergeUsersView(FormView): @@ -103,8 +107,10 @@ class MergeUsersView(FormView): raise PermissionDenied def form_valid(self, form): - self.final_user = merge_users(form.cleaned_data['user1'], form.cleaned_data['user2']) + self.final_user = merge_users( + form.cleaned_data["user1"], form.cleaned_data["user2"] + ) return super(MergeUsersView, self).form_valid(form) def get_success_url(self): - return reverse('core:user_profile', kwargs={'user_id': self.final_user.id}) + return reverse("core:user_profile", kwargs={"user_id": self.final_user.id}) diff --git a/sas/__init__.py b/sas/__init__.py index 0a9419f8..0ace29c4 100644 --- a/sas/__init__.py +++ b/sas/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/sas/migrations/0001_initial.py b/sas/migrations/0001_initial.py index 573a7274..a089648b 100644 --- a/sas/migrations/0001_initial.py +++ b/sas/migrations/0001_initial.py @@ -6,27 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0006_auto_20161108_1703'), - ] + dependencies = [("core", "0006_auto_20161108_1703")] operations = [ migrations.CreateModel( - name='Album', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('core.sithfile',), + name="Album", fields=[], options={"proxy": True}, bases=("core.sithfile",) ), migrations.CreateModel( - name='Picture', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('core.sithfile',), + name="Picture", fields=[], options={"proxy": True}, bases=("core.sithfile",) ), ] diff --git a/sas/migrations/0002_auto_20161119_1241.py b/sas/migrations/0002_auto_20161119_1241.py index ff51d24c..ed759ebc 100644 --- a/sas/migrations/0002_auto_20161119_1241.py +++ b/sas/migrations/0002_auto_20161119_1241.py @@ -9,20 +9,39 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('sas', '0001_initial'), + ("sas", "0001_initial"), ] operations = [ migrations.CreateModel( - name='PeoplePictureRelation', + name="PeoplePictureRelation", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('picture', models.ForeignKey(related_name='people', to='sas.Picture', verbose_name='picture')), - ('user', models.ForeignKey(related_name='pictures', to=settings.AUTH_USER_MODEL, verbose_name='user')), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + auto_created=True, + serialize=False, + ), + ), + ( + "picture", + models.ForeignKey( + related_name="people", to="sas.Picture", verbose_name="picture" + ), + ), + ( + "user", + models.ForeignKey( + related_name="pictures", + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), ], ), migrations.AlterUniqueTogether( - name='peoplepicturerelation', - unique_together=set([('user', 'picture')]), + name="peoplepicturerelation", unique_together=set([("user", "picture")]) ), ] diff --git a/sas/models.py b/sas/models.py index dafe9ece..a92d41c6 100644 --- a/sas/models.py +++ b/sas/models.py @@ -38,14 +38,20 @@ from core.utils import resize_image, exif_auto_rotate class SASPictureManager(models.Manager): def get_queryset(self): - return super(SASPictureManager, self).get_queryset().filter(is_in_sas=True, - is_folder=False) + return ( + super(SASPictureManager, self) + .get_queryset() + .filter(is_in_sas=True, is_folder=False) + ) class SASAlbumManager(models.Manager): def get_queryset(self): - return super(SASAlbumManager, self).get_queryset().filter(is_in_sas=True, - is_folder=True) + return ( + super(SASAlbumManager, self) + .get_queryset() + .filter(is_in_sas=True, is_folder=True) + ) class Picture(SithFile): @@ -56,7 +62,9 @@ class Picture(SithFile): @property def is_vertical(self): - with open(os.path.join(settings.MEDIA_ROOT, self.file.name).encode('utf-8'), 'rb') as f: + with open( + os.path.join(settings.MEDIA_ROOT, self.file.name).encode("utf-8"), "rb" + ) as f: im = Image.open(BytesIO(f.read())) (w, h) = im.size return (w / h) < 1 @@ -64,24 +72,27 @@ class Picture(SithFile): def can_be_edited_by(self, user): # file = SithFile.objects.filter(id=self.id).first() - return user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) # or user.can_edit(file) + return user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ) # or user.can_edit(file) def can_be_viewed_by(self, user): # file = SithFile.objects.filter(id=self.id).first() - return self.can_be_edited_by(user) or (self.is_in_sas and self.is_moderated and - user.was_subscribed) # or user.can_view(file) + return self.can_be_edited_by(user) or ( + self.is_in_sas and self.is_moderated and user.was_subscribed + ) # or user.can_view(file) def get_download_url(self): - return reverse('sas:download', kwargs={'picture_id': self.id}) + return reverse("sas:download", kwargs={"picture_id": self.id}) def get_download_compressed_url(self): - return reverse('sas:download_compressed', kwargs={'picture_id': self.id}) + return reverse("sas:download_compressed", kwargs={"picture_id": self.id}) def get_download_thumb_url(self): - return reverse('sas:download_thumb', kwargs={'picture_id': self.id}) + return reverse("sas:download_thumb", kwargs={"picture_id": self.id}) def get_absolute_url(self): - return reverse('sas:picture', kwargs={'picture_id': self.id}) + return reverse("sas:picture", kwargs={"picture_id": self.id}) def generate_thumbnails(self, overwrite=False): im = Image.open(BytesIO(self.file.read())) @@ -89,9 +100,9 @@ class Picture(SithFile): im = exif_auto_rotate(im) except: pass - file = resize_image(im, max(im.size), self.mime_type.split('/')[-1]) - thumb = resize_image(im, 200, self.mime_type.split('/')[-1]) - compressed = resize_image(im, 1200, self.mime_type.split('/')[-1]) + file = resize_image(im, max(im.size), self.mime_type.split("/")[-1]) + thumb = resize_image(im, 200, self.mime_type.split("/")[-1]) + compressed = resize_image(im, 1200, self.mime_type.split("/")[-1]) if overwrite: self.file.delete() self.thumbnail.delete() @@ -105,28 +116,60 @@ class Picture(SithFile): self.save() def rotate(self, degree): - for attr in ['file', 'compressed', 'thumbnail']: + for attr in ["file", "compressed", "thumbnail"]: name = self.__getattribute__(attr).name - with open(os.path.join(settings.MEDIA_ROOT, name).encode('utf-8'), 'r+b') as file: + with open( + os.path.join(settings.MEDIA_ROOT, name).encode("utf-8"), "r+b" + ) as file: if file: im = Image.open(BytesIO(file.read())) file.seek(0) im = im.rotate(degree, expand=True) - im.save(fp=file, format=self.mime_type.split('/')[-1].upper(), quality=90, optimize=True, progressive=True) + im.save( + fp=file, + format=self.mime_type.split("/")[-1].upper(), + quality=90, + optimize=True, + progressive=True, + ) def get_next(self): if self.is_moderated: - return self.parent.children.filter(is_moderated=True, asked_for_removal=False, is_folder=False, - id__gt=self.id).order_by('id').first() + return ( + self.parent.children.filter( + is_moderated=True, + asked_for_removal=False, + is_folder=False, + id__gt=self.id, + ) + .order_by("id") + .first() + ) else: - return Picture.objects.filter(id__gt=self.id, is_moderated=False).order_by('id').first() + return ( + Picture.objects.filter(id__gt=self.id, is_moderated=False) + .order_by("id") + .first() + ) def get_previous(self): if self.is_moderated: - return self.parent.children.filter(is_moderated=True, asked_for_removal=False, is_folder=False, - id__lt=self.id).order_by('id').last() + return ( + self.parent.children.filter( + is_moderated=True, + asked_for_removal=False, + is_folder=False, + id__lt=self.id, + ) + .order_by("id") + .last() + ) else: - return Picture.objects.filter(id__lt=self.id, is_moderated=False).order_by('-id').first() + return ( + Picture.objects.filter(id__lt=self.id, is_moderated=False) + .order_by("-id") + .first() + ) class Album(SithFile): @@ -145,25 +188,34 @@ class Album(SithFile): def can_be_edited_by(self, user): # file = SithFile.objects.filter(id=self.id).first() - return user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) # or user.can_edit(file) + return user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ) # or user.can_edit(file) def can_be_viewed_by(self, user): # file = SithFile.objects.filter(id=self.id).first() - return self.can_be_edited_by(user) or (self.is_in_sas and self.is_moderated and - user.was_subscribed) # or user.can_view(file) + return self.can_be_edited_by(user) or ( + self.is_in_sas and self.is_moderated and user.was_subscribed + ) # or user.can_view(file) def get_absolute_url(self): - return reverse('sas:album', kwargs={'album_id': self.id}) + return reverse("sas:album", kwargs={"album_id": self.id}) def get_download_url(self): - return reverse('sas:album_preview', kwargs={'album_id': self.id}) + return reverse("sas:album_preview", kwargs={"album_id": self.id}) def generate_thumbnail(self): - p = self.children_pictures.order_by('?').first() or self.children_albums.exclude(file=None).exclude(file="").order_by('?').first() + p = ( + self.children_pictures.order_by("?").first() + or self.children_albums.exclude(file=None) + .exclude(file="") + .order_by("?") + .first() + ) if p and p.file: im = Image.open(BytesIO(p.file.read())) self.file = resize_image(im, 200, "jpeg") - self.file.name = self.name + '/thumb.jpg' + self.file.name = self.name + "/thumb.jpg" self.save() @@ -182,11 +234,20 @@ class PeoplePictureRelation(models.Model): The PeoplePictureRelation class makes the connection between User and Picture """ - user = models.ForeignKey(User, verbose_name=_('user'), related_name="pictures", null=False, blank=False) - picture = models.ForeignKey(Picture, verbose_name=_('picture'), related_name="people", null=False, blank=False) + + user = models.ForeignKey( + User, verbose_name=_("user"), related_name="pictures", null=False, blank=False + ) + picture = models.ForeignKey( + Picture, + verbose_name=_("picture"), + related_name="people", + null=False, + blank=False, + ) class Meta: - unique_together = ['user', 'picture'] + unique_together = ["user", "picture"] def __str__(self): return self.user.get_display_name() + " - " + str(self.picture) diff --git a/sas/urls.py b/sas/urls.py index e4804292..7b53c0ec 100644 --- a/sas/urls.py +++ b/sas/urls.py @@ -27,17 +27,35 @@ from django.conf.urls import url from sas.views import * urlpatterns = [ - url(r'^$', SASMainView.as_view(), name='main'), - url(r'^moderation$', ModerationView.as_view(), name='moderation'), - url(r'^album/(?P[0-9]+)$', AlbumView.as_view(), name='album'), - url(r'^album/(?P[0-9]+)/upload$', AlbumUploadView.as_view(), name='album_upload'), - url(r'^album/(?P[0-9]+)/edit$', AlbumEditView.as_view(), name='album_edit'), - url(r'^album/(?P[0-9]+)/preview$', send_album, name='album_preview'), - url(r'^picture/(?P[0-9]+)$', PictureView.as_view(), name='picture'), - url(r'^picture/(?P[0-9]+)/edit$', PictureEditView.as_view(), name='picture_edit'), - url(r'^picture/(?P[0-9]+)/download$', send_pict, name='download'), - url(r'^picture/(?P[0-9]+)/download/compressed$', send_compressed, name='download_compressed'), - url(r'^picture/(?P[0-9]+)/download/thumb$', send_thumb, name='download_thumb'), + url(r"^$", SASMainView.as_view(), name="main"), + url(r"^moderation$", ModerationView.as_view(), name="moderation"), + url(r"^album/(?P[0-9]+)$", AlbumView.as_view(), name="album"), + url( + r"^album/(?P[0-9]+)/upload$", + AlbumUploadView.as_view(), + name="album_upload", + ), + url( + r"^album/(?P[0-9]+)/edit$", AlbumEditView.as_view(), name="album_edit" + ), + url(r"^album/(?P[0-9]+)/preview$", send_album, name="album_preview"), + url(r"^picture/(?P[0-9]+)$", PictureView.as_view(), name="picture"), + url( + r"^picture/(?P[0-9]+)/edit$", + PictureEditView.as_view(), + name="picture_edit", + ), + url(r"^picture/(?P[0-9]+)/download$", send_pict, name="download"), + url( + r"^picture/(?P[0-9]+)/download/compressed$", + send_compressed, + name="download_compressed", + ), + url( + r"^picture/(?P[0-9]+)/download/thumb$", + send_thumb, + name="download_thumb", + ), # url(r'^album/new$', AlbumCreateView.as_view(), name='album_new'), # url(r'^(?P[0-9]+)/$', ClubView.as_view(), name='club_view'), ] diff --git a/sas/views.py b/sas/views.py index e6d20e10..859b4a7f 100644 --- a/sas/views.py +++ b/sas/views.py @@ -44,22 +44,43 @@ from sas.models import Picture, Album, PeoplePictureRelation class SASForm(forms.Form): - album_name = forms.CharField(label=_("Add a new album"), max_length=30, required=False) - images = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Upload images"), - required=False) + album_name = forms.CharField( + label=_("Add a new album"), max_length=30, required=False + ) + images = forms.ImageField( + widget=forms.ClearableFileInput(attrs={"multiple": True}), + label=_("Upload images"), + required=False, + ) def process(self, parent, owner, files, automodere=False): try: - if self.cleaned_data['album_name'] != "": - album = Album(parent=parent, name=self.cleaned_data['album_name'], owner=owner, is_moderated=automodere) + if self.cleaned_data["album_name"] != "": + album = Album( + parent=parent, + name=self.cleaned_data["album_name"], + owner=owner, + is_moderated=automodere, + ) album.clean() album.save() except Exception as e: - self.add_error(None, _("Error creating album %(album)s: %(msg)s") % - {'album': self.cleaned_data['album_name'], 'msg': repr(e)}) + self.add_error( + None, + _("Error creating album %(album)s: %(msg)s") + % {"album": self.cleaned_data["album_name"], "msg": repr(e)}, + ) for f in files: - new_file = Picture(parent=parent, name=f.name, file=f, owner=owner, mime_type=f.content_type, size=f._size, - is_folder=False, is_moderated=automodere) + new_file = Picture( + parent=parent, + name=f.name, + file=f, + owner=owner, + mime_type=f.content_type, + size=f._size, + is_folder=False, + is_moderated=automodere, + ) if automodere: new_file.moderator = owner try: @@ -67,30 +88,41 @@ class SASForm(forms.Form): new_file.generate_thumbnails() new_file.save() except Exception as e: - self.add_error(None, _("Error uploading file %(file_name)s: %(msg)s") % {'file_name': f, 'msg': repr(e)}) + self.add_error( + None, + _("Error uploading file %(file_name)s: %(msg)s") + % {"file_name": f, "msg": repr(e)}, + ) class RelationForm(forms.ModelForm): class Meta: model = PeoplePictureRelation - fields = ['picture'] - widgets = {'picture': forms.HiddenInput} - users = AutoCompleteSelectMultipleField('users', show_help_text=False, help_text="", label=_("Add user"), required=False) + fields = ["picture"] + widgets = {"picture": forms.HiddenInput} + + users = AutoCompleteSelectMultipleField( + "users", show_help_text=False, help_text="", label=_("Add user"), required=False + ) class SASMainView(FormView): form_class = SASForm template_name = "sas/main.jinja" - success_url = reverse_lazy('sas:main') + success_url = reverse_lazy("sas:main") def post(self, request, *args, **kwargs): self.form = self.get_form() parent = SithFile.objects.filter(id=settings.SITH_SAS_ROOT_DIR_ID).first() - files = request.FILES.getlist('images') + files = request.FILES.getlist("images") root = User.objects.filter(username="root").first() - if request.user.is_authenticated() and request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID): + if request.user.is_authenticated() and request.user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ): if self.form.is_valid(): - self.form.process(parent=parent, owner=root, files=files, automodere=True) + self.form.process( + parent=parent, owner=root, files=files, automodere=True + ) if self.form.is_valid(): return super(SASMainView, self).form_valid(self.form) else: @@ -99,8 +131,10 @@ class SASMainView(FormView): def get_context_data(self, **kwargs): kwargs = super(SASMainView, self).get_context_data(**kwargs) - kwargs['categories'] = Album.objects.filter(parent__id=settings.SITH_SAS_ROOT_DIR_ID).order_by('id') - kwargs['latest'] = Album.objects.filter(is_moderated=True).order_by('-id')[:5] + kwargs["categories"] = Album.objects.filter( + parent__id=settings.SITH_SAS_ROOT_DIR_ID + ).order_by("id") + kwargs["latest"] = Album.objects.filter(is_moderated=True).order_by("-id")[:5] return kwargs @@ -111,23 +145,27 @@ class PictureView(CanViewMixin, DetailView, FormMixin): template_name = "sas/picture.jinja" def get_initial(self): - return {'picture': self.object} + return {"picture": self.object} def get(self, request, *args, **kwargs): self.object = self.get_object() self.form = self.get_form() - if 'rotate_right' in request.GET.keys(): + if "rotate_right" in request.GET.keys(): self.object.rotate(270) - if 'rotate_left' in request.GET.keys(): + if "rotate_left" in request.GET.keys(): self.object.rotate(90) - if 'remove_user' in request.GET.keys(): + if "remove_user" in request.GET.keys(): try: - user = User.objects.filter(id=int(request.GET['remove_user'])).first() - if user.id == request.user.id or request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID): - PeoplePictureRelation.objects.filter(user=user, picture=self.object).delete() + user = User.objects.filter(id=int(request.GET["remove_user"])).first() + if user.id == request.user.id or request.user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ): + PeoplePictureRelation.objects.filter( + user=user, picture=self.object + ).delete() except: pass - if 'ask_removal' in request.GET.keys(): + if "ask_removal" in request.GET.keys(): self.object.is_moderated = False self.object.asked_for_removal = True self.object.save() @@ -139,12 +177,19 @@ class PictureView(CanViewMixin, DetailView, FormMixin): self.form = self.get_form() if request.user.is_authenticated() and request.user.was_subscribed: if self.form.is_valid(): - for uid in self.form.cleaned_data['users']: + for uid in self.form.cleaned_data["users"]: u = User.objects.filter(id=uid).first() - PeoplePictureRelation(user=u, - picture=self.form.cleaned_data['picture']).save() - if not u.notifications.filter(type="NEW_PICTURES", viewed=False).exists(): - Notification(user=u, url=reverse("core:user_pictures", kwargs={'user_id': u.id}), type="NEW_PICTURES").save() + PeoplePictureRelation( + user=u, picture=self.form.cleaned_data["picture"] + ).save() + if not u.notifications.filter( + type="NEW_PICTURES", viewed=False + ).exists(): + Notification( + user=u, + url=reverse("core:user_pictures", kwargs={"user_id": u.id}), + type="NEW_PICTURES", + ).save() return super(PictureView, self).form_valid(self.form) else: self.form.add_error(None, _("You do not have the permission to do that")) @@ -152,16 +197,17 @@ class PictureView(CanViewMixin, DetailView, FormMixin): def get_context_data(self, **kwargs): kwargs = super(PictureView, self).get_context_data(**kwargs) - kwargs['form'] = self.form + kwargs["form"] = self.form return kwargs def get_success_url(self): - return reverse('sas:picture', kwargs={'picture_id': self.object.id}) + return reverse("sas:picture", kwargs={"picture_id": self.object.id}) def send_album(request, album_id): return send_file(request, album_id, Album) + def send_pict(request, picture_id): return send_file(request, picture_id, Picture) @@ -185,11 +231,17 @@ class AlbumUploadView(CanViewMixin, DetailView, FormMixin): self.object.generate_thumbnail() self.form = self.get_form() parent = SithFile.objects.filter(id=self.object.id).first() - files = request.FILES.getlist('images') + files = request.FILES.getlist("images") if request.user.is_authenticated() and request.user.is_subscribed: if self.form.is_valid(): - self.form.process(parent=parent, owner=request.user, files=files, - automodere=request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID)) + self.form.process( + parent=parent, + owner=request.user, + files=files, + automodere=request.user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ), + ) if self.form.is_valid(): return HttpResponse(str(self.form.errors), status=200) return HttpResponse(str(self.form.errors), status=500) @@ -203,8 +255,8 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): def get(self, request, *args, **kwargs): self.form = self.get_form() - if 'clipboard' not in request.session.keys(): - request.session['clipboard'] = [] + if "clipboard" not in request.session.keys(): + request.session["clipboard"] = [] return super(AlbumView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): @@ -212,16 +264,22 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): if not self.object.file: self.object.generate_thumbnail() self.form = self.get_form() - if 'clipboard' not in request.session.keys(): - request.session['clipboard'] = [] + if "clipboard" not in request.session.keys(): + request.session["clipboard"] = [] if request.user.can_edit(self.object): # Handle the copy-paste functions FileView.handle_clipboard(request, self.object) parent = SithFile.objects.filter(id=self.object.id).first() - files = request.FILES.getlist('images') + files = request.FILES.getlist("images") if request.user.is_authenticated() and request.user.is_subscribed: if self.form.is_valid(): - self.form.process(parent=parent, owner=request.user, files=files, - automodere=request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID)) + self.form.process( + parent=parent, + owner=request.user, + files=files, + automodere=request.user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ), + ) if self.form.is_valid(): return super(AlbumView, self).form_valid(self.form) else: @@ -229,14 +287,17 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): return self.form_invalid(self.form) def get_success_url(self): - return reverse('sas:album', kwargs={'album_id': self.object.id}) + return reverse("sas:album", kwargs={"album_id": self.object.id}) def get_context_data(self, **kwargs): kwargs = super(AlbumView, self).get_context_data(**kwargs) - kwargs['form'] = self.form - kwargs['clipboard'] = SithFile.objects.filter(id__in=self.request.session['clipboard']) + kwargs["form"] = self.form + kwargs["clipboard"] = SithFile.objects.filter( + id__in=self.request.session["clipboard"] + ) return kwargs + # Admin views @@ -251,12 +312,12 @@ class ModerationView(TemplateView): def post(self, request, *args, **kwargs): if request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID): try: - a = Album.objects.filter(id=request.POST['album_id']).first() - if 'moderate' in request.POST.keys(): + a = Album.objects.filter(id=request.POST["album_id"]).first() + if "moderate" in request.POST.keys(): a.moderator = request.user a.is_moderated = True a.save() - elif 'delete' in request.POST.keys(): + elif "delete" in request.POST.keys(): a.delete() except: pass @@ -264,45 +325,52 @@ class ModerationView(TemplateView): def get_context_data(self, **kwargs): kwargs = super(ModerationView, self).get_context_data(**kwargs) - kwargs['albums_to_moderate'] = Album.objects.filter(is_moderated=False, is_in_sas=True, - is_folder=True).order_by('id') - kwargs['pictures'] = Picture.objects.filter(is_moderated=False, is_in_sas=True, is_folder=False) - kwargs['albums'] = Album.objects.filter(id__in=kwargs['pictures'].values('parent').distinct('parent')) + kwargs["albums_to_moderate"] = Album.objects.filter( + is_moderated=False, is_in_sas=True, is_folder=True + ).order_by("id") + kwargs["pictures"] = Picture.objects.filter( + is_moderated=False, is_in_sas=True, is_folder=False + ) + kwargs["albums"] = Album.objects.filter( + id__in=kwargs["pictures"].values("parent").distinct("parent") + ) return kwargs class PictureEditForm(forms.ModelForm): class Meta: model = Picture - fields = ['name', 'parent'] - parent = make_ajax_field(Picture, 'parent', 'files', help_text="") + fields = ["name", "parent"] + + parent = make_ajax_field(Picture, "parent", "files", help_text="") class AlbumEditForm(forms.ModelForm): class Meta: model = Album - fields = ['name', 'date', 'file', 'parent', 'edit_groups'] + fields = ["name", "date", "file", "parent", "edit_groups"] + date = forms.DateField(label=_("Date"), widget=SelectDate, required=True) - parent = make_ajax_field(Album, 'parent', 'files', help_text="") - edit_groups = make_ajax_field(Album, 'edit_groups', 'groups', help_text="") + parent = make_ajax_field(Album, "parent", "files", help_text="") + edit_groups = make_ajax_field(Album, "edit_groups", "groups", help_text="") recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) class PictureEditView(CanEditMixin, UpdateView): model = Picture form_class = PictureEditForm - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" pk_url_kwarg = "picture_id" class AlbumEditView(CanEditMixin, UpdateView): model = Album form_class = AlbumEditForm - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" pk_url_kwarg = "album_id" def form_valid(self, form): ret = super(AlbumEditView, self).form_valid(form) - if form.cleaned_data['recursive']: + if form.cleaned_data["recursive"]: self.object.apply_rights_recursively(True) return ret diff --git a/sith/__init__.py b/sith/__init__.py index 0a9419f8..0ace29c4 100644 --- a/sith/__init__.py +++ b/sith/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/sith/settings.py b/sith/settings.py index e40bc343..af52ebcb 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -43,19 +43,19 @@ from django.utils.translation import ugettext_lazy as _ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -os.environ['HTTPS'] = "off" +os.environ["HTTPS"] = "off" # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2' +SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False -INTERNAL_IPS = ['127.0.0.1'] +INTERNAL_IPS = ["127.0.0.1"] -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] # Application definition @@ -63,50 +63,50 @@ ALLOWED_HOSTS = ['*'] SITE_ID = 4000 INSTALLED_APPS = ( - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.sites', - 'django_jinja', - 'rest_framework', - 'ajax_select', - 'haystack', - 'captcha', - 'core', - 'club', - 'subscription', - 'accounting', - 'counter', - 'eboutic', - 'launderette', - 'api', - 'rootplace', - 'sas', - 'com', - 'election', - 'forum', - 'stock', - 'trombi', - 'matmat', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", + "django_jinja", + "rest_framework", + "ajax_select", + "haystack", + "captcha", + "core", + "club", + "subscription", + "accounting", + "counter", + "eboutic", + "launderette", + "api", + "rootplace", + "sas", + "com", + "election", + "forum", + "stock", + "trombi", + "matmat", ) MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'core.middleware.AuthenticationMiddleware', + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.auth.middleware.SessionAuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "django.middleware.security.SecurityMiddleware", + "core.middleware.AuthenticationMiddleware", ) -ROOT_URLCONF = 'sith.urls' +ROOT_URLCONF = "sith.urls" TEMPLATES = [ { @@ -163,44 +163,44 @@ TEMPLATES = [ "autoescape": True, "auto_reload": True, "translation_engine": "django.utils.translation", - } + }, }, { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ] }, }, ] HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', - 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), - }, + "default": { + "ENGINE": "haystack.backends.whoosh_backend.WhooshEngine", + "PATH": os.path.join(os.path.dirname(__file__), "whoosh_index"), + } } -HAYSTACK_SIGNAL_PROCESSOR = 'core.search_indexes.UserOnlySignalProcessor' +HAYSTACK_SIGNAL_PROCESSOR = "core.search_indexes.UserOnlySignalProcessor" SASS_PRECISION = 8 -WSGI_APPLICATION = 'sith.wsgi.application' +WSGI_APPLICATION = "sith.wsgi.application" # Database # https://docs.djangoproject.com/en/1.8/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } @@ -208,14 +208,11 @@ DATABASES = { # Internationalization # https://docs.djangoproject.com/en/1.8/topics/i18n/ -LANGUAGE_CODE = 'fr-FR' +LANGUAGE_CODE = "fr-FR" -LANGUAGES = [ - ('en', _('English')), - ('fr', _('French')), -] +LANGUAGES = [("en", _("English")), ("fr", _("French"))] -TIME_ZONE = 'Europe/Paris' +TIME_ZONE = "Europe/Paris" USE_I18N = True @@ -223,9 +220,7 @@ USE_L10N = True USE_TZ = True -LOCALE_PATHS = ( - os.path.join(BASE_DIR, "locale"), -) +LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),) PHONENUMBER_DEFAULT_REGION = "FR" @@ -233,33 +228,33 @@ PHONENUMBER_DEFAULT_REGION = "FR" EXTERNAL_RES = True # Medias -MEDIA_ROOT = './data/' -MEDIA_URL = '/data/' +MEDIA_ROOT = "./data/" +MEDIA_URL = "/data/" # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.8/howto/static-files/ -STATIC_URL = '/static/' -STATIC_ROOT = './static/' +STATIC_URL = "/static/" +STATIC_ROOT = "./static/" # Static files finders which allow to see static folder in all apps STATICFILES_FINDERS = [ - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'core.scss.finder.ScssFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", + "core.scss.finder.ScssFinder", ] # Auth configuration -AUTH_USER_MODEL = 'core.User' -AUTH_ANONYMOUS_MODEL = 'core.models.AnonymousUser' -LOGIN_URL = '/login' -LOGOUT_URL = '/logout' -LOGIN_REDIRECT_URL = '/' +AUTH_USER_MODEL = "core.User" +AUTH_ANONYMOUS_MODEL = "core.models.AnonymousUser" +LOGIN_URL = "/login" +LOGOUT_URL = "/logout" +LOGIN_REDIRECT_URL = "/" DEFAULT_FROM_EMAIL = "bibou@git.an" SITH_COM_EMAIL = "bibou_com@git.an" # Email -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_HOST = "localhost" EMAIL_PORT = 25 @@ -267,12 +262,12 @@ EMAIL_PORT = 25 IS_OLD_MYSQL_PRESENT = False OLD_MYSQL_INFOS = { - 'host': 'ae-db', - 'user': "my_user", - 'passwd': "password", - 'db': "ae2-db", - 'charset': 'utf8', - 'use_unicode': True, + "host": "ae-db", + "user": "my_user", + "passwd": "password", + "db": "ae2-db", + "charset": "utf8", + "use_unicode": True, } @@ -281,25 +276,27 @@ SITH_NAME = "Sith website" SITH_TWITTER = "@ae_utbm" # AE configuration -SITH_MAIN_CLUB_ID = 1 # TODO: keep only that first setting, with the ID, and do the same for the other clubs +SITH_MAIN_CLUB_ID = ( + 1 +) # TODO: keep only that first setting, with the ID, and do the same for the other clubs SITH_MAIN_CLUB = { - 'name': "AE", - 'unix_name': "ae", - 'address': "6 Boulevard Anatole France, 90000 Belfort" + "name": "AE", + "unix_name": "ae", + "address": "6 Boulevard Anatole France, 90000 Belfort", } # Bar managers SITH_BAR_MANAGER = { - 'name': "BdF", - 'unix_name': "bdf", - 'address': "6 Boulevard Anatole France, 90000 Belfort" + "name": "BdF", + "unix_name": "bdf", + "address": "6 Boulevard Anatole France, 90000 Belfort", } # Launderette managers SITH_LAUNDERETTE_MANAGER = { - 'name': "Laverie", - 'unix_name': "laverie", - 'address': "6 Boulevard Anatole France, 90000 Belfort" + "name": "Laverie", + "unix_name": "laverie", + "address": "6 Boulevard Anatole France, 90000 Belfort", } # Main root for club pages @@ -343,9 +340,9 @@ SITH_SAS_ROOT_DIR_ID = 4 SITH_BOARD_SUFFIX = "-bureau" SITH_MEMBER_SUFFIX = "-membres" -SITH_MAIN_BOARD_GROUP = SITH_MAIN_CLUB['unix_name'] + SITH_BOARD_SUFFIX -SITH_MAIN_MEMBERS_GROUP = SITH_MAIN_CLUB['unix_name'] + SITH_MEMBER_SUFFIX -SITH_BAR_MANAGER_BOARD_GROUP = SITH_BAR_MANAGER['unix_name'] + SITH_BOARD_SUFFIX +SITH_MAIN_BOARD_GROUP = SITH_MAIN_CLUB["unix_name"] + SITH_BOARD_SUFFIX +SITH_MAIN_MEMBERS_GROUP = SITH_MAIN_CLUB["unix_name"] + SITH_MEMBER_SUFFIX +SITH_BAR_MANAGER_BOARD_GROUP = SITH_BAR_MANAGER["unix_name"] + SITH_BOARD_SUFFIX SITH_PROFILE_DEPARTMENTS = [ ("TC", _("TC")), @@ -364,50 +361,46 @@ SITH_PROFILE_DEPARTMENTS = [ ] SITH_ACCOUNTING_PAYMENT_METHOD = [ - ('CHECK', _('Check')), - ('CASH', _('Cash')), - ('TRANSFERT', _('Transfert')), - ('CARD', _('Credit card')), + ("CHECK", _("Check")), + ("CASH", _("Cash")), + ("TRANSFERT", _("Transfert")), + ("CARD", _("Credit card")), ] SITH_SUBSCRIPTION_PAYMENT_METHOD = [ - ('CHECK', _('Check')), - ('CARD', _('Credit card')), - ('CASH', _('Cash')), - ('EBOUTIC', _('Eboutic')), - ('OTHER', _('Other')), + ("CHECK", _("Check")), + ("CARD", _("Credit card")), + ("CASH", _("Cash")), + ("EBOUTIC", _("Eboutic")), + ("OTHER", _("Other")), ] SITH_SUBSCRIPTION_LOCATIONS = [ - ('BELFORT', _('Belfort')), - ('SEVENANS', _('Sevenans')), - ('MONTBELIARD', _('Montbéliard')), - ('EBOUTIC', _('Eboutic')), + ("BELFORT", _("Belfort")), + ("SEVENANS", _("Sevenans")), + ("MONTBELIARD", _("Montbéliard")), + ("EBOUTIC", _("Eboutic")), ] -SITH_COUNTER_BARS = [ - (1, "MDE"), - (2, "Foyer"), - (35, "La Gommette"), -] +SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")] SITH_COUNTER_PAYMENT_METHOD = [ - ('CHECK', _('Check')), - ('CASH', _('Cash')), - ('CARD', _('Credit card')), + ("CHECK", _("Check")), + ("CASH", _("Cash")), + ("CARD", _("Credit card")), ] SITH_COUNTER_BANK = [ - ('OTHER', 'Autre'), - ('SOCIETE-GENERALE', 'Société générale'), - ('BANQUE-POPULAIRE', 'Banque populaire'), - ('BNP', 'BNP'), - ('CAISSE-EPARGNE', 'Caisse d\'épargne'), - ('CIC', 'CIC'), - ('CREDIT-AGRICOLE', 'Crédit Agricole'), - ('CREDIT-MUTUEL', 'Credit Mutuel'), - ('CREDIT-LYONNAIS', 'Credit Lyonnais'), - ('LA-POSTE', 'La Poste'), + ("OTHER", "Autre"), + ("SOCIETE-GENERALE", "Société générale"), + ("BANQUE-POPULAIRE", "Banque populaire"), + ("BNP", "BNP"), + ("CAISSE-EPARGNE", "Caisse d'épargne"), + ("CIC", "CIC"), + ("CREDIT-AGRICOLE", "Crédit Agricole"), + ("CREDIT-MUTUEL", "Credit Mutuel"), + ("CREDIT-LYONNAIS", "Credit Lyonnais"), + ("LA-POSTE", "La Poste"), ] SITH_ECOCUP_CONS = 1152 @@ -428,9 +421,7 @@ SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = 1 SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = 2 SITH_PRODUCTTYPE_SUBSCRIPTION = 2 -SITH_CAN_CREATE_SUBSCRIPTIONS = [ - 1, -] +SITH_CAN_CREATE_SUBSCRIPTIONS = [1] # Number of weeks before the end of a subscription when the subscriber can resubscribe SITH_SUBSCRIPTION_END = 10 @@ -438,113 +429,61 @@ SITH_SUBSCRIPTION_END = 10 # Subscription durations are in semestres # Be careful, modifying this parameter will need a migration to be applied SITH_SUBSCRIPTIONS = { - 'un-semestre': { - 'name': _('One semester'), - 'price': 15, - 'duration': 1, + "un-semestre": {"name": _("One semester"), "price": 15, "duration": 1}, + "deux-semestres": {"name": _("Two semesters"), "price": 28, "duration": 2}, + "cursus-tronc-commun": { + "name": _("Common core cursus"), + "price": 45, + "duration": 4, }, - 'deux-semestres': { - 'name': _('Two semesters'), - 'price': 28, - 'duration': 2, + "cursus-branche": {"name": _("Branch cursus"), "price": 45, "duration": 6}, + "cursus-alternant": {"name": _("Alternating cursus"), "price": 30, "duration": 6}, + "membre-honoraire": {"name": _("Honorary member"), "price": 0, "duration": 666}, + "assidu": {"name": _("Assidu member"), "price": 0, "duration": 2}, + "amicale/doceo": {"name": _("Amicale/DOCEO member"), "price": 0, "duration": 2}, + "reseau-ut": {"name": _("UT network member"), "price": 0, "duration": 1}, + "crous": {"name": _("CROUS member"), "price": 0, "duration": 2}, + "sbarro/esta": {"name": _("Sbarro/ESTA member"), "price": 15, "duration": 2}, + "un-semestre-welcome": { + "name": _("One semester Welcome Week"), + "price": 0, + "duration": 1, }, - 'cursus-tronc-commun': { - 'name': _('Common core cursus'), - 'price': 45, - 'duration': 4, + "deux-mois-essai": {"name": _("Two months for free"), "price": 0, "duration": 0.33}, + "benevoles-euroks": {"name": _("Eurok's volunteer"), "price": 5, "duration": 0.1}, + "six-semaines-essai": { + "name": _("Six weeks for free"), + "price": 0, + "duration": 0.23, }, - 'cursus-branche': { - 'name': _('Branch cursus'), - 'price': 45, - 'duration': 6, - }, - 'cursus-alternant': { - 'name': _('Alternating cursus'), - 'price': 30, - 'duration': 6, - }, - 'membre-honoraire': { - 'name': _('Honorary member'), - 'price': 0, - 'duration': 666, - }, - 'assidu': { - 'name': _('Assidu member'), - 'price': 0, - 'duration': 2, - }, - 'amicale/doceo': { - 'name': _('Amicale/DOCEO member'), - 'price': 0, - 'duration': 2, - }, - 'reseau-ut': { - 'name': _('UT network member'), - 'price': 0, - 'duration': 1, - }, - 'crous': { - 'name': _('CROUS member'), - 'price': 0, - 'duration': 2, - }, - 'sbarro/esta': { - 'name': _('Sbarro/ESTA member'), - 'price': 15, - 'duration': 2, - }, - 'un-semestre-welcome': { - 'name': _('One semester Welcome Week'), - 'price': 0, - 'duration': 1, - }, - 'deux-mois-essai': { - 'name': _('Two months for free'), - 'price': 0, - 'duration': 0.33, - }, - 'benevoles-euroks': { - 'name': _('Eurok\'s volunteer'), - 'price': 5, - 'duration': 0.1 - }, - 'six-semaines-essai': { - 'name': _('Six weeks for free'), - 'price': 0, - 'duration': 0.23, - }, - 'un-jour': { - 'name': _('One day'), - 'price': 0, - 'duration': 0.00555333, - } + "un-jour": {"name": _("One day"), "price": 0, "duration": 0.00555333} # To be completed.... } SITH_CLUB_ROLES = {} SITH_CLUB_ROLES_ID = { - 'President': 10, - 'Vice-President': 9, - 'Treasurer': 7, - 'Communication supervisor': 5, - 'Secretary': 4, - 'IT supervisor': 3, - 'Board member': 2, - 'Active member': 1, - 'Curious': 0, + "President": 10, + "Vice-President": 9, + "Treasurer": 7, + "Communication supervisor": 5, + "Secretary": 4, + "IT supervisor": 3, + "Board member": 2, + "Active member": 1, + "Curious": 0, } SITH_CLUB_ROLES = { - 10: _('President'), - 9: _('Vice-President'), - 7: _('Treasurer'), - 5: _('Communication supervisor'), - 4: _('Secretary'), - 3: _('IT supervisor'), - 2: _('Board member'), - 1: _('Active member'), - 0: _('Curious'), + 10: _("President"), + 9: _("Vice-President"), + 7: _("Treasurer"), + 5: _("Communication supervisor"), + 4: _("Secretary"), + 3: _("IT supervisor"), + 2: _("Board member"), + 1: _("Active member"), + 0: _("Curious"), } # This corresponds to the maximum role a user can freely subscribe to @@ -562,85 +501,85 @@ SITH_COUNTER_MINUTE_INACTIVE = 10 # ET variables SITH_EBOUTIC_CB_ENABLED = True -SITH_EBOUTIC_ET_URL = "https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi" +SITH_EBOUTIC_ET_URL = ( + "https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi" +) SITH_EBOUTIC_PBX_SITE = "4000666" SITH_EBOUTIC_PBX_RANG = "42" SITH_EBOUTIC_PBX_IDENTIFIANT = "123456789" -SITH_EBOUTIC_HMAC_KEY = binascii.unhexlify("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF") +SITH_EBOUTIC_HMAC_KEY = binascii.unhexlify( + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" +) SITH_EBOUTIC_PUB_KEY = "" -with open('./sith/et_keys/pubkey.pem') as f: +with open("./sith/et_keys/pubkey.pem") as f: SITH_EBOUTIC_PUB_KEY = f.read() # Launderette variables -SITH_LAUNDERETTE_MACHINE_TYPES = [('WASHING', _('Washing')), ('DRYING', _('Drying'))] -SITH_LAUNDERETTE_PRICES = { - 'WASHING': 1.0, - 'DRYING': 0.75, -} +SITH_LAUNDERETTE_MACHINE_TYPES = [("WASHING", _("Washing")), ("DRYING", _("Drying"))] +SITH_LAUNDERETTE_PRICES = {"WASHING": 1.0, "DRYING": 0.75} SITH_NOTIFICATIONS = [ - ('POSTER_MODERATION', _("A new poster needs to be moderated")), - ('MAILING_MODERATION', _("A new mailing list needs to be moderated")), - ('NEWS_MODERATION', _("There are %s fresh news to be moderated")), - ('FILE_MODERATION', _("New files to be moderated")), - ('SAS_MODERATION', _("There are %s pictures to be moderated in the SAS")), - ('NEW_PICTURES', _("You've been identified on some pictures")), - ('REFILLING', _("You just refilled of %s €")), - ('SELLING', _("You just bought %s")), - ('GENERIC', _("You have a notification")), + ("POSTER_MODERATION", _("A new poster needs to be moderated")), + ("MAILING_MODERATION", _("A new mailing list needs to be moderated")), + ("NEWS_MODERATION", _("There are %s fresh news to be moderated")), + ("FILE_MODERATION", _("New files to be moderated")), + ("SAS_MODERATION", _("There are %s pictures to be moderated in the SAS")), + ("NEW_PICTURES", _("You've been identified on some pictures")), + ("REFILLING", _("You just refilled of %s €")), + ("SELLING", _("You just bought %s")), + ("GENERIC", _("You have a notification")), ] # The keys are the notification names as found in SITH_NOTIFICATIONS, and the # values are the callback function to update the notifs. # The callback must take the notif object as first and single argument. SITH_PERMANENT_NOTIFICATIONS = { - 'NEWS_MODERATION': 'com.models.news_notification_callback', - 'SAS_MODERATION': 'sas.models.sas_notification_callback', + "NEWS_MODERATION": "com.models.news_notification_callback", + "SAS_MODERATION": "sas.models.sas_notification_callback", } SITH_QUICK_NOTIF = { - 'qn_success': _("Success!"), - 'qn_fail': _("Fail!"), - 'qn_weekmail_new_article': _("You successfully posted an article in the Weekmail"), - 'qn_weekmail_article_edit': _("You successfully edited an article in the Weekmail"), - 'qn_weekmail_send_success': _("You successfully sent the Weekmail"), + "qn_success": _("Success!"), + "qn_fail": _("Fail!"), + "qn_weekmail_new_article": _("You successfully posted an article in the Weekmail"), + "qn_weekmail_article_edit": _("You successfully edited an article in the Weekmail"), + "qn_weekmail_send_success": _("You successfully sent the Weekmail"), } # Mailing related settings -SITH_MAILING_DOMAIN = 'utbm.fr' -SITH_MAILING_FETCH_KEY = 'IloveMails' +SITH_MAILING_DOMAIN = "utbm.fr" +SITH_MAILING_FETCH_KEY = "IloveMails" -SITH_GIFT_LIST = [ - ('AE Tee-shirt', _("AE tee-shirt")) -] +SITH_GIFT_LIST = [("AE Tee-shirt", _("AE tee-shirt"))] try: from .settings_custom import * + print("Custom settings imported", file=sys.stderr) except: print("Custom settings failed", file=sys.stderr) if DEBUG: INSTALLED_APPS += ("debug_toolbar",) - MIDDLEWARE_CLASSES = ('debug_toolbar.middleware.DebugToolbarMiddleware',) + MIDDLEWARE_CLASSES + MIDDLEWARE_CLASSES = ( + "debug_toolbar.middleware.DebugToolbarMiddleware", + ) + MIDDLEWARE_CLASSES DEBUG_TOOLBAR_PANELS = [ - 'debug_toolbar.panels.versions.VersionsPanel', - 'debug_toolbar.panels.timer.TimerPanel', - 'debug_toolbar.panels.settings.SettingsPanel', - 'debug_toolbar.panels.headers.HeadersPanel', - 'debug_toolbar.panels.request.RequestPanel', - 'debug_toolbar.panels.sql.SQLPanel', - 'debug_toolbar.panels.staticfiles.StaticFilesPanel', - 'sith.toolbar_debug.TemplatesPanel', - 'debug_toolbar.panels.cache.CachePanel', - 'debug_toolbar.panels.signals.SignalsPanel', - 'debug_toolbar.panels.logging.LoggingPanel', - 'debug_toolbar.panels.redirects.RedirectsPanel', - ] - SASS_INCLUDE_FOLDERS = [ - 'core/static/', + "debug_toolbar.panels.versions.VersionsPanel", + "debug_toolbar.panels.timer.TimerPanel", + "debug_toolbar.panels.settings.SettingsPanel", + "debug_toolbar.panels.headers.HeadersPanel", + "debug_toolbar.panels.request.RequestPanel", + "debug_toolbar.panels.sql.SQLPanel", + "debug_toolbar.panels.staticfiles.StaticFilesPanel", + "sith.toolbar_debug.TemplatesPanel", + "debug_toolbar.panels.cache.CachePanel", + "debug_toolbar.panels.signals.SignalsPanel", + "debug_toolbar.panels.logging.LoggingPanel", + "debug_toolbar.panels.redirects.RedirectsPanel", ] + SASS_INCLUDE_FOLDERS = ["core/static/"] -if 'test' in sys.argv: +if "test" in sys.argv: CAPTCHA_TEST_MODE = True diff --git a/sith/toolbar_debug.py b/sith/toolbar_debug.py index 9fb29ef3..03b531d5 100644 --- a/sith/toolbar_debug.py +++ b/sith/toolbar_debug.py @@ -27,7 +27,7 @@ from debug_toolbar.panels.templates import TemplatesPanel as BaseTemplatesPanel class TemplatesPanel(BaseTemplatesPanel): def generate_stats(self, *args): - template = self.templates[0]['template'] - if not hasattr(template, 'engine') and hasattr(template, 'backend'): + template = self.templates[0]["template"] + if not hasattr(template, "engine") and hasattr(template, "backend"): template.engine = template.backend return super().generate_stats(*args) diff --git a/sith/urls.py b/sith/urls.py index af443b9c..9b85e64e 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -44,41 +44,55 @@ from django.conf import settings from django.views.i18n import javascript_catalog from ajax_select import urls as ajax_select_urls -js_info_dict = { - 'packages': ('sith',), -} +js_info_dict = {"packages": ("sith",)} handler403 = "core.views.forbidden" handler404 = "core.views.not_found" urlpatterns = [ - url(r'^', include('core.urls', namespace="core", app_name="core")), - url(r'^rootplace/', include('rootplace.urls', namespace="rootplace", app_name="rootplace")), - url(r'^subscription/', include('subscription.urls', namespace="subscription", app_name="subscription")), - url(r'^com/', include('com.urls', namespace="com", app_name="com")), - url(r'^club/', include('club.urls', namespace="club", app_name="club")), - url(r'^counter/', include('counter.urls', namespace="counter", app_name="counter")), - url(r'^stock/', include('stock.urls', namespace="stock", app_name="stock")), - url(r'^accounting/', include('accounting.urls', namespace="accounting", app_name="accounting")), - url(r'^eboutic/', include('eboutic.urls', namespace="eboutic", app_name="eboutic")), - url(r'^launderette/', include('launderette.urls', namespace="launderette", app_name="launderette")), - url(r'^sas/', include('sas.urls', namespace="sas", app_name="sas")), - url(r'^api/v1/', include('api.urls', namespace="api", app_name="api")), - url(r'^election/', include('election.urls', namespace="election", app_name="election")), - url(r'^forum/', include('forum.urls', namespace="forum", app_name="forum")), - url(r'^trombi/', include('trombi.urls', namespace="trombi", app_name="trombi")), - url(r'^matmatronch/', include('matmat.urls', namespace="matmat", app_name="matmat")), - url(r'^admin/', include(admin.site.urls)), - url(r'^ajax_select/', include(ajax_select_urls)), - url(r'^i18n/', include('django.conf.urls.i18n')), - url(r'^jsi18n/$', javascript_catalog, js_info_dict, name='javascript-catalog'), - url(r'^captcha/', include('captcha.urls')), + url(r"^", include("core.urls", namespace="core", app_name="core")), + url( + r"^rootplace/", + include("rootplace.urls", namespace="rootplace", app_name="rootplace"), + ), + url( + r"^subscription/", + include("subscription.urls", namespace="subscription", app_name="subscription"), + ), + url(r"^com/", include("com.urls", namespace="com", app_name="com")), + url(r"^club/", include("club.urls", namespace="club", app_name="club")), + url(r"^counter/", include("counter.urls", namespace="counter", app_name="counter")), + url(r"^stock/", include("stock.urls", namespace="stock", app_name="stock")), + url( + r"^accounting/", + include("accounting.urls", namespace="accounting", app_name="accounting"), + ), + url(r"^eboutic/", include("eboutic.urls", namespace="eboutic", app_name="eboutic")), + url( + r"^launderette/", + include("launderette.urls", namespace="launderette", app_name="launderette"), + ), + url(r"^sas/", include("sas.urls", namespace="sas", app_name="sas")), + url(r"^api/v1/", include("api.urls", namespace="api", app_name="api")), + url( + r"^election/", + include("election.urls", namespace="election", app_name="election"), + ), + url(r"^forum/", include("forum.urls", namespace="forum", app_name="forum")), + url(r"^trombi/", include("trombi.urls", namespace="trombi", app_name="trombi")), + url( + r"^matmatronch/", include("matmat.urls", namespace="matmat", app_name="matmat") + ), + url(r"^admin/", include(admin.site.urls)), + url(r"^ajax_select/", include(ajax_select_urls)), + url(r"^i18n/", include("django.conf.urls.i18n")), + url(r"^jsi18n/$", javascript_catalog, js_info_dict, name="javascript-catalog"), + url(r"^captcha/", include("captcha.urls")), ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) import debug_toolbar - urlpatterns += [ - url(r'^__debug__/', include(debug_toolbar.urls)), - ] + + urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))] diff --git a/sith/wsgi.py b/sith/wsgi.py index be0024b7..b440975f 100644 --- a/sith/wsgi.py +++ b/sith/wsgi.py @@ -36,6 +36,6 @@ import os from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sith.settings") -os.environ['HTTPS'] = "on" +os.environ["HTTPS"] = "on" application = get_wsgi_application() diff --git a/stock/__init__.py b/stock/__init__.py index 6871e12c..6a83e48e 100644 --- a/stock/__init__.py +++ b/stock/__init__.py @@ -22,4 +22,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/stock/admin.py b/stock/admin.py index 6f55f86e..46567a3b 100644 --- a/stock/admin.py +++ b/stock/admin.py @@ -31,4 +31,4 @@ from stock.models import Stock, StockItem, ShoppingList, ShoppingListItem admin.site.register(Stock) admin.site.register(StockItem) admin.site.register(ShoppingList) -admin.site.register(ShoppingListItem) \ No newline at end of file +admin.site.register(ShoppingListItem) diff --git a/stock/migrations/0001_initial.py b/stock/migrations/0001_initial.py index 9aa4170e..686d7e65 100644 --- a/stock/migrations/0001_initial.py +++ b/stock/migrations/0001_initial.py @@ -7,64 +7,170 @@ import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('counter', '0011_auto_20161004_2039'), - ] + dependencies = [("counter", "0011_auto_20161004_2039")] operations = [ migrations.CreateModel( - name='ShoppingList', + name="ShoppingList", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('date', models.DateTimeField(verbose_name='date')), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('todo', models.BooleanField(verbose_name='todo')), - ('comment', models.TextField(verbose_name='comment', blank=True, null=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("date", models.DateTimeField(verbose_name="date")), + ("name", models.CharField(max_length=64, verbose_name="name")), + ("todo", models.BooleanField(verbose_name="todo")), + ( + "comment", + models.TextField(verbose_name="comment", blank=True, null=True), + ), ], ), migrations.CreateModel( - name='ShoppingListItem', + name="ShoppingListItem", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('tobuy_quantity', models.IntegerField(verbose_name='quantity to buy', help_text='quantity to buy during the next shopping session', default=6)), - ('bought_quantity', models.IntegerField(verbose_name='quantity bought', help_text='quantity bought during the last shopping session', default=0)), - ('shopping_lists', models.ManyToManyField(verbose_name='shopping lists', related_name='shopping_items_to_buy', to='stock.ShoppingList')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "tobuy_quantity", + models.IntegerField( + verbose_name="quantity to buy", + help_text="quantity to buy during the next shopping session", + default=6, + ), + ), + ( + "bought_quantity", + models.IntegerField( + verbose_name="quantity bought", + help_text="quantity bought during the last shopping session", + default=0, + ), + ), + ( + "shopping_lists", + models.ManyToManyField( + verbose_name="shopping lists", + related_name="shopping_items_to_buy", + to="stock.ShoppingList", + ), + ), ], ), migrations.CreateModel( - name='Stock', + name="Stock", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('counter', models.OneToOneField(verbose_name='counter', related_name='stock', to='counter.Counter')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "counter", + models.OneToOneField( + verbose_name="counter", + related_name="stock", + to="counter.Counter", + ), + ), ], ), migrations.CreateModel( - name='StockItem', + name="StockItem", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('unit_quantity', models.IntegerField(verbose_name='unit quantity', help_text='number of element in one box', default=0)), - ('effective_quantity', models.IntegerField(verbose_name='effective quantity', help_text='number of box', default=0)), - ('minimal_quantity', models.IntegerField(verbose_name='minimal quantity', help_text='if the effective quantity is less than the minimal, item is added to the shopping list', default=1)), - ('stock_owner', models.ForeignKey(related_name='items', to='stock.Stock')), - ('type', models.ForeignKey(blank=True, null=True, verbose_name='type', related_name='stock_items', on_delete=django.db.models.deletion.SET_NULL, to='counter.ProductType')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "unit_quantity", + models.IntegerField( + verbose_name="unit quantity", + help_text="number of element in one box", + default=0, + ), + ), + ( + "effective_quantity", + models.IntegerField( + verbose_name="effective quantity", + help_text="number of box", + default=0, + ), + ), + ( + "minimal_quantity", + models.IntegerField( + verbose_name="minimal quantity", + help_text="if the effective quantity is less than the minimal, item is added to the shopping list", + default=1, + ), + ), + ( + "stock_owner", + models.ForeignKey(related_name="items", to="stock.Stock"), + ), + ( + "type", + models.ForeignKey( + blank=True, + null=True, + verbose_name="type", + related_name="stock_items", + on_delete=django.db.models.deletion.SET_NULL, + to="counter.ProductType", + ), + ), ], ), migrations.AddField( - model_name='shoppinglistitem', - name='stockitem_owner', - field=models.ForeignKey(null=True, related_name='shopping_item', to='stock.StockItem'), + model_name="shoppinglistitem", + name="stockitem_owner", + field=models.ForeignKey( + null=True, related_name="shopping_item", to="stock.StockItem" + ), ), migrations.AddField( - model_name='shoppinglistitem', - name='type', - field=models.ForeignKey(blank=True, null=True, verbose_name='type', related_name='shoppinglist_items', on_delete=django.db.models.deletion.SET_NULL, to='counter.ProductType'), + model_name="shoppinglistitem", + name="type", + field=models.ForeignKey( + blank=True, + null=True, + verbose_name="type", + related_name="shoppinglist_items", + on_delete=django.db.models.deletion.SET_NULL, + to="counter.ProductType", + ), ), migrations.AddField( - model_name='shoppinglist', - name='stock_owner', - field=models.ForeignKey(null=True, related_name='shopping_lists', to='stock.Stock'), + model_name="shoppinglist", + name="stock_owner", + field=models.ForeignKey( + null=True, related_name="shopping_lists", to="stock.Stock" + ), ), ] diff --git a/stock/models.py b/stock/models.py index b2e0c161..381278b2 100644 --- a/stock/models.py +++ b/stock/models.py @@ -31,59 +31,82 @@ from django.conf import settings from counter.models import Counter, ProductType + class Stock(models.Model): """ The Stock class, this one is used to know how many products are left for a specific counter """ - name = models.CharField(_('name'), max_length=64) - counter = models.OneToOneField(Counter, verbose_name=_('counter'), related_name='stock') + + name = models.CharField(_("name"), max_length=64) + counter = models.OneToOneField( + Counter, verbose_name=_("counter"), related_name="stock" + ) def __str__(self): return "%s (%s)" % (self.name, self.counter) def get_absolute_url(self): - return reverse('stock:list') + return reverse("stock:list") def can_be_viewed_by(self, user): return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) + class StockItem(models.Model): """ The StockItem class, element of the stock """ - name = models.CharField(_('name'), max_length=64) - unit_quantity = models.IntegerField(_('unit quantity'), default=0, help_text=_('number of element in one box')) - effective_quantity = models.IntegerField(_('effective quantity'), default=0, help_text=_('number of box')) - minimal_quantity = models.IntegerField(_('minimal quantity'), default=1, - help_text=_('if the effective quantity is less than the minimal, item is added to the shopping list')) - type = models.ForeignKey(ProductType, related_name="stock_items", verbose_name=_("type"), null=True, blank=True, - on_delete=models.SET_NULL) + + name = models.CharField(_("name"), max_length=64) + unit_quantity = models.IntegerField( + _("unit quantity"), default=0, help_text=_("number of element in one box") + ) + effective_quantity = models.IntegerField( + _("effective quantity"), default=0, help_text=_("number of box") + ) + minimal_quantity = models.IntegerField( + _("minimal quantity"), + default=1, + help_text=_( + "if the effective quantity is less than the minimal, item is added to the shopping list" + ), + ) + type = models.ForeignKey( + ProductType, + related_name="stock_items", + verbose_name=_("type"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) stock_owner = models.ForeignKey(Stock, related_name="items") def __str__(self): return "%s" % (self.name) def get_absolute_url(self): - return reverse('stock:items_list', kwargs={'stock_id':self.stock_owner.id}) + return reverse("stock:items_list", kwargs={"stock_id": self.stock_owner.id}) def can_be_viewed_by(self, user): return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) + class ShoppingList(models.Model): """ The ShoppingList class, used to make an history of the shopping lists """ - date = models.DateTimeField(_('date')) - name = models.CharField(_('name'), max_length=64) - todo = models.BooleanField(_('todo')) - comment = models.TextField(_('comment'), null=True, blank=True) + + date = models.DateTimeField(_("date")) + name = models.CharField(_("name"), max_length=64) + todo = models.BooleanField(_("todo")) + comment = models.TextField(_("comment"), null=True, blank=True) stock_owner = models.ForeignKey(Stock, null=True, related_name="shopping_lists") def __str__(self): return "%s (%s)" % (self.name, self.date) def get_absolute_url(self): - return reverse('stock:shoppinglist_list') + return reverse("stock:shoppinglist_list") def can_be_viewed_by(self, user): return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) @@ -92,13 +115,34 @@ class ShoppingList(models.Model): class ShoppingListItem(models.Model): """ """ - shopping_lists = models.ManyToManyField(ShoppingList, verbose_name=_("shopping lists"), related_name="shopping_items_to_buy") - stockitem_owner = models.ForeignKey(StockItem, related_name="shopping_item", null=True) - name = models.CharField(_('name'), max_length=64) - type = models.ForeignKey(ProductType, related_name="shoppinglist_items", verbose_name=_("type"), null=True, blank=True, - on_delete=models.SET_NULL) - tobuy_quantity = models.IntegerField(_('quantity to buy'), default=6, help_text=_("quantity to buy during the next shopping session")) - bought_quantity = models.IntegerField(_('quantity bought'), default=0, help_text=_("quantity bought during the last shopping session")) + + shopping_lists = models.ManyToManyField( + ShoppingList, + verbose_name=_("shopping lists"), + related_name="shopping_items_to_buy", + ) + stockitem_owner = models.ForeignKey( + StockItem, related_name="shopping_item", null=True + ) + name = models.CharField(_("name"), max_length=64) + type = models.ForeignKey( + ProductType, + related_name="shoppinglist_items", + verbose_name=_("type"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + tobuy_quantity = models.IntegerField( + _("quantity to buy"), + default=6, + help_text=_("quantity to buy during the next shopping session"), + ) + bought_quantity = models.IntegerField( + _("quantity bought"), + default=0, + help_text=_("quantity bought during the last shopping session"), + ) def __str__(self): return "%s - %s" % (self.name, self.shopping_lists.first()) @@ -107,5 +151,4 @@ class ShoppingListItem(models.Model): return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) def get_absolute_url(self): - return reverse('stock:shoppinglist_list') - + return reverse("stock:shoppinglist_list") diff --git a/stock/urls.py b/stock/urls.py index ea0ffb3c..ac85d147 100644 --- a/stock/urls.py +++ b/stock/urls.py @@ -28,28 +28,61 @@ from django.conf.urls import include, url from stock.views import * urlpatterns = [ - #Stock urls - url(r'^new/counter/(?P[0-9]+)$', StockCreateView.as_view(), name='new'), - url(r'^edit/(?P[0-9]+)$', StockEditView.as_view(), name='edit'), - url(r'^list$', StockListView.as_view(), name='list'), - - # StockItem urls - url(r'^(?P[0-9]+)$', StockItemList.as_view(), name='items_list'), - url(r'^(?P[0-9]+)/stock_item/new_item$', StockItemCreateView.as_view(), name='new_item'), - url(r'^stock_item/(?P[0-9]+)/edit$', StockItemEditView.as_view(), name='edit_item'), - url(r'^(?P[0-9]+)/stock_item/take_items$', StockTakeItemsBaseFormView.as_view(), name='take_items'), - - # ShoppingList urls - url(r'^(?P[0-9]+)/shopping_list/list$', StockShoppingListView.as_view(), name='shoppinglist_list'), - url(r'^(?P[0-9]+)/shopping_list/create$', StockItemQuantityBaseFormView.as_view(), name='shoppinglist_create'), - url(r'^(?P[0-9]+)/shopping_list/(?P[0-9]+)/items$', StockShoppingListItemListView.as_view(), - name='shoppinglist_items'), - url(r'^(?P[0-9]+)/shopping_list/(?P[0-9]+)/delete$', StockShoppingListDeleteView.as_view(), - name='shoppinglist_delete'), - url(r'^(?P[0-9]+)/shopping_list/(?P[0-9]+)/set_done$', StockShopppingListSetDone.as_view(), - name='shoppinglist_set_done'), - url(r'^(?P[0-9]+)/shopping_list/(?P[0-9]+)/set_todo$', StockShopppingListSetTodo.as_view(), - name='shoppinglist_set_todo'), - url(r'^(?P[0-9]+)/shopping_list/(?P[0-9]+)/update_stock$', StockUpdateAfterShopppingBaseFormView.as_view(), - name='update_after_shopping'), - ] + # Stock urls + url(r"^new/counter/(?P[0-9]+)$", StockCreateView.as_view(), name="new"), + url(r"^edit/(?P[0-9]+)$", StockEditView.as_view(), name="edit"), + url(r"^list$", StockListView.as_view(), name="list"), + # StockItem urls + url(r"^(?P[0-9]+)$", StockItemList.as_view(), name="items_list"), + url( + r"^(?P[0-9]+)/stock_item/new_item$", + StockItemCreateView.as_view(), + name="new_item", + ), + url( + r"^stock_item/(?P[0-9]+)/edit$", + StockItemEditView.as_view(), + name="edit_item", + ), + url( + r"^(?P[0-9]+)/stock_item/take_items$", + StockTakeItemsBaseFormView.as_view(), + name="take_items", + ), + # ShoppingList urls + url( + r"^(?P[0-9]+)/shopping_list/list$", + StockShoppingListView.as_view(), + name="shoppinglist_list", + ), + url( + r"^(?P[0-9]+)/shopping_list/create$", + StockItemQuantityBaseFormView.as_view(), + name="shoppinglist_create", + ), + url( + r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/items$", + StockShoppingListItemListView.as_view(), + name="shoppinglist_items", + ), + url( + r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/delete$", + StockShoppingListDeleteView.as_view(), + name="shoppinglist_delete", + ), + url( + r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/set_done$", + StockShopppingListSetDone.as_view(), + name="shoppinglist_set_done", + ), + url( + r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/set_todo$", + StockShopppingListSetTodo.as_view(), + name="shoppinglist_set_todo", + ), + url( + r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/update_stock$", + StockUpdateAfterShopppingBaseFormView.as_view(), + name="update_after_shopping", + ), +] diff --git a/stock/views.py b/stock/views.py index 5ce349d3..6a911164 100644 --- a/stock/views.py +++ b/stock/views.py @@ -29,7 +29,14 @@ from datetime import datetime, timedelta from django.utils import timezone from django.shortcuts import render, get_object_or_404 from django.views.generic import ListView, DetailView, RedirectView, TemplateView -from django.views.generic.edit import UpdateView, CreateView, DeleteView, ProcessFormView, FormMixin, BaseFormView +from django.views.generic.edit import ( + UpdateView, + CreateView, + DeleteView, + ProcessFormView, + FormMixin, + BaseFormView, +) from django.utils.translation import ugettext_lazy as _ from django import forms from django.http import HttpResponseRedirect, HttpResponse @@ -37,7 +44,13 @@ from django.forms.models import modelform_factory from django.core.urlresolvers import reverse_lazy, reverse from django.db import transaction, DataError -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin +from core.views import ( + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + CanCreateMixin, + TabedViewMixin, +) from counter.views import CounterAdminTabsMixin, CounterTabsMixin from counter.models import Counter, ProductType from stock.models import Stock, StockItem, ShoppingList, ShoppingListItem @@ -47,23 +60,26 @@ class StockItemList(CounterAdminTabsMixin, CanCreateMixin, ListView): """ The stockitems list view for the counter owner """ + model = Stock - template_name = 'stock/stock_item_list.jinja' + template_name = "stock/stock_item_list.jinja" pk_url_kwarg = "stock_id" current_tab = "stocks" def get_context_data(self): ret = super(StockItemList, self).get_context_data() - if 'stock_id' in self.kwargs.keys(): - ret['stock'] = Stock.objects.filter(id=self.kwargs['stock_id']).first(); + if "stock_id" in self.kwargs.keys(): + ret["stock"] = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return ret + class StockListView(CounterAdminTabsMixin, CanViewMixin, ListView): """ A list view for the admins """ + model = Stock - template_name = 'stock/stock_list.jinja' + template_name = "stock/stock_list.jinja" current_tab = "stocks" @@ -71,9 +87,10 @@ class StockEditForm(forms.ModelForm): """ A form to change stock's characteristics """ + class Meta: model = Stock - fields = ['name', 'counter'] + fields = ["name", "counter"] def __init__(self, *args, **kwargs): super(StockEditForm, self).__init__(*args, **kwargs) @@ -86,10 +103,11 @@ class StockEditView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView): """ An edit view for the stock """ + model = Stock - form_class = modelform_factory(Stock, fields=['name', 'counter']) + form_class = modelform_factory(Stock, fields=["name", "counter"]) pk_url_kwarg = "stock_id" - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "stocks" @@ -97,10 +115,21 @@ class StockItemEditView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView): """ An edit view for a stock item """ + model = StockItem - form_class = modelform_factory(StockItem, fields=['name', 'unit_quantity', 'effective_quantity', 'minimal_quantity', 'type', 'stock_owner']) + form_class = modelform_factory( + StockItem, + fields=[ + "name", + "unit_quantity", + "effective_quantity", + "minimal_quantity", + "type", + "stock_owner", + ], + ) pk_url_kwarg = "item_id" - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "stocks" @@ -108,43 +137,59 @@ class StockCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView): """ A create view for a new Stock """ + model = Stock - form_class = modelform_factory(Stock, fields=['name', 'counter']) - template_name = 'core/create.jinja' + form_class = modelform_factory(Stock, fields=["name", "counter"]) + template_name = "core/create.jinja" pk_url_kwarg = "counter_id" current_tab = "stocks" - success_url = reverse_lazy('stock:list') + success_url = reverse_lazy("stock:list") def get_initial(self): ret = super(StockCreateView, self).get_initial() - if 'counter_id' in self.kwargs.keys(): - ret['counter'] = self.kwargs['counter_id'] + if "counter_id" in self.kwargs.keys(): + ret["counter"] = self.kwargs["counter_id"] return ret + class StockItemCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView): """ A create view for a new StockItem """ + model = StockItem - form_class = modelform_factory(StockItem, fields=['name', 'unit_quantity', 'effective_quantity', 'minimal_quantity', 'type', 'stock_owner']) - template_name = 'core/create.jinja' + form_class = modelform_factory( + StockItem, + fields=[ + "name", + "unit_quantity", + "effective_quantity", + "minimal_quantity", + "type", + "stock_owner", + ], + ) + template_name = "core/create.jinja" pk_url_kwarg = "stock_id" current_tab = "stocks" def get_initial(self): ret = super(StockItemCreateView, self).get_initial() - if 'stock_id' in self.kwargs.keys(): - ret['stock_owner'] = self.kwargs['stock_id'] + if "stock_id" in self.kwargs.keys(): + ret["stock_owner"] = self.kwargs["stock_id"] return ret def get_success_url(self): - return reverse_lazy('stock:items_list', kwargs={'stock_id':self.object.stock_owner.id}) + return reverse_lazy( + "stock:items_list", kwargs={"stock_id": self.object.stock_owner.id} + ) class StockShoppingListView(CounterAdminTabsMixin, CanViewMixin, ListView): """ A list view for the people to know the item to buy """ + model = Stock template_name = "stock/stock_shopping_list.jinja" pk_url_kwarg = "stock_id" @@ -152,8 +197,8 @@ class StockShoppingListView(CounterAdminTabsMixin, CanViewMixin, ListView): def get_context_data(self): ret = super(StockShoppingListView, self).get_context_data() - if 'stock_id' in self.kwargs.keys(): - ret['stock'] = Stock.objects.filter(id=self.kwargs['stock_id']).first(); + if "stock_id" in self.kwargs.keys(): + ret["stock"] = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return ret @@ -161,32 +206,45 @@ class StockItemQuantityForm(forms.BaseForm): def clean(self): with transaction.atomic(): self.stock = Stock.objects.filter(id=self.stock_id).first() - shopping_list = ShoppingList(name="Courses "+self.stock.counter.name, date=timezone.now(), todo=True) + shopping_list = ShoppingList( + name="Courses " + self.stock.counter.name, + date=timezone.now(), + todo=True, + ) shopping_list.save() shopping_list.stock_owner = self.stock shopping_list.save() - for k,t in self.cleaned_data.items(): - if k == 'name': + for k, t in self.cleaned_data.items(): + if k == "name": shopping_list.name = t shopping_list.save() elif k == "comment": shopping_list.comment = t shopping_list.save() else: - if t > 0 : + if t > 0: item_id = int(k[5:]) item = StockItem.objects.filter(id=item_id).first() - shoppinglist_item = ShoppingListItem(stockitem_owner=item, name=item.name, type=item.type, tobuy_quantity=t) + shoppinglist_item = ShoppingListItem( + stockitem_owner=item, + name=item.name, + type=item.type, + tobuy_quantity=t, + ) shoppinglist_item.save() shoppinglist_item.shopping_lists.add(shopping_list) shoppinglist_item.save() return self.cleaned_data -class StockItemQuantityBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailView, BaseFormView): + +class StockItemQuantityBaseFormView( + CounterAdminTabsMixin, CanEditMixin, DetailView, BaseFormView +): """ docstring for StockItemOutList """ + model = StockItem template_name = "stock/shopping_list_quantity.jinja" pk_url_kwarg = "stock_id" @@ -195,24 +253,39 @@ class StockItemQuantityBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailV def get_form_class(self): fields = OrderedDict() kwargs = {} - fields['name'] = forms.CharField(max_length=30, required=True, label=_('Shopping list name')) - for t in ProductType.objects.order_by('name').all(): - for i in self.stock.items.filter(type=t).order_by('name').all(): + fields["name"] = forms.CharField( + max_length=30, required=True, label=_("Shopping list name") + ) + for t in ProductType.objects.order_by("name").all(): + for i in self.stock.items.filter(type=t).order_by("name").all(): if i.effective_quantity <= i.minimal_quantity: field_name = "item-%s" % (str(i.id)) - fields[field_name] = forms.IntegerField(required=True, label=str(i), initial=0, - help_text=_(str(i.effective_quantity)+" left")) - fields['comment'] = forms.CharField(widget=forms.Textarea(attrs={"placeholder":_("Add here, items to buy that are not reference as a stock item (example : sponge, knife, mugs ...)")}), - required=False, label=_("Comments")) - kwargs['stock_id'] = self.stock.id - kwargs['base_fields'] = fields - return type('StockItemQuantityForm', (StockItemQuantityForm,), kwargs) + fields[field_name] = forms.IntegerField( + required=True, + label=str(i), + initial=0, + help_text=_(str(i.effective_quantity) + " left"), + ) + fields["comment"] = forms.CharField( + widget=forms.Textarea( + attrs={ + "placeholder": _( + "Add here, items to buy that are not reference as a stock item (example : sponge, knife, mugs ...)" + ) + } + ), + required=False, + label=_("Comments"), + ) + kwargs["stock_id"] = self.stock.id + kwargs["base_fields"] = fields + return type("StockItemQuantityForm", (StockItemQuantityForm,), kwargs) def get(self, request, *args, **kwargs): """ Simple get view """ - self.stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() + self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return super(StockItemQuantityBaseFormView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): @@ -220,7 +293,7 @@ class StockItemQuantityBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailV Handle the many possibilities of the post request """ self.object = self.get_object() - self.stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() + self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return super(StockItemQuantityBaseFormView, self).post(request, *args, **kwargs) def form_valid(self, form): @@ -228,17 +301,20 @@ class StockItemQuantityBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailV def get_context_data(self, **kwargs): kwargs = super(StockItemQuantityBaseFormView, self).get_context_data(**kwargs) - if 'form' not in kwargs.keys(): - kwargs['form'] = self.get_form() - kwargs['stock'] = self.stock + if "form" not in kwargs.keys(): + kwargs["form"] = self.get_form() + kwargs["stock"] = self.stock return kwargs def get_success_url(self): - return reverse_lazy('stock:shoppinglist_list', args=self.args, kwargs=self.kwargs) + return reverse_lazy( + "stock:shoppinglist_list", args=self.args, kwargs=self.kwargs + ) class StockShoppingListItemListView(CounterAdminTabsMixin, CanViewMixin, ListView): """docstring for StockShoppingListItemListView""" + model = ShoppingList template_name = "stock/shopping_list_items.jinja" pk_url_kwarg = "shoppinglist_id" @@ -246,27 +322,34 @@ class StockShoppingListItemListView(CounterAdminTabsMixin, CanViewMixin, ListVie def get_context_data(self): ret = super(StockShoppingListItemListView, self).get_context_data() - if 'shoppinglist_id' in self.kwargs.keys(): - ret['shoppinglist'] = ShoppingList.objects.filter(id=self.kwargs['shoppinglist_id']).first(); + if "shoppinglist_id" in self.kwargs.keys(): + ret["shoppinglist"] = ShoppingList.objects.filter( + id=self.kwargs["shoppinglist_id"] + ).first() return ret + class StockShoppingListDeleteView(CounterAdminTabsMixin, CanEditMixin, DeleteView): """ Delete a ShoppingList (for the resonsible account) """ + model = ShoppingList pk_url_kwarg = "shoppinglist_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" current_tab = "stocks" def get_success_url(self): - return reverse_lazy('stock:shoppinglist_list', kwargs={'stock_id':self.object.stock_owner.id}) + return reverse_lazy( + "stock:shoppinglist_list", kwargs={"stock_id": self.object.stock_owner.id} + ) class StockShopppingListSetDone(CanEditMixin, DetailView): """ Set a ShoppingList as done """ + model = ShoppingList pk_url_kwarg = "shoppinglist_id" @@ -274,17 +357,30 @@ class StockShopppingListSetDone(CanEditMixin, DetailView): self.object = self.get_object() self.object.todo = False self.object.save() - return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + return HttpResponseRedirect( + reverse( + "stock:shoppinglist_list", + args=self.args, + kwargs={"stock_id": self.object.stock_owner.id}, + ) + ) def post(self, request, *args, **kwargs): self.object = self.get_object() - return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + return HttpResponseRedirect( + reverse( + "stock:shoppinglist_list", + args=self.args, + kwargs={"stock_id": self.object.stock_owner.id}, + ) + ) class StockShopppingListSetTodo(CanEditMixin, DetailView): """ Set a ShoppingList as done """ + model = ShoppingList pk_url_kwarg = "shoppinglist_id" @@ -292,21 +388,37 @@ class StockShopppingListSetTodo(CanEditMixin, DetailView): self.object = self.get_object() self.object.todo = True self.object.save() - return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + return HttpResponseRedirect( + reverse( + "stock:shoppinglist_list", + args=self.args, + kwargs={"stock_id": self.object.stock_owner.id}, + ) + ) def post(self, request, *args, **kwargs): self.object = self.get_object() - return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + return HttpResponseRedirect( + reverse( + "stock:shoppinglist_list", + args=self.args, + kwargs={"stock_id": self.object.stock_owner.id}, + ) + ) class StockUpdateAfterShopppingForm(forms.BaseForm): def clean(self): with transaction.atomic(): - self.shoppinglist = ShoppingList.objects.filter(id=self.shoppinglist_id).first() - for k,t in self.cleaned_data.items(): + self.shoppinglist = ShoppingList.objects.filter( + id=self.shoppinglist_id + ).first() + for k, t in self.cleaned_data.items(): shoppinglist_item_id = int(k[5:]) - if int(t) > 0 : - shoppinglist_item = ShoppingListItem.objects.filter(id=shoppinglist_item_id).first() + if int(t) > 0: + shoppinglist_item = ShoppingListItem.objects.filter( + id=shoppinglist_item_id + ).first() shoppinglist_item.bought_quantity = int(t) shoppinglist_item.save() shoppinglist_item.stockitem_owner.effective_quantity += int(t) @@ -315,10 +427,14 @@ class StockUpdateAfterShopppingForm(forms.BaseForm): self.shoppinglist.save() return self.cleaned_data -class StockUpdateAfterShopppingBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailView, BaseFormView): + +class StockUpdateAfterShopppingBaseFormView( + CounterAdminTabsMixin, CanEditMixin, DetailView, BaseFormView +): """ docstring for StockUpdateAfterShopppingBaseFormView """ + model = ShoppingList template_name = "stock/update_after_shopping.jinja" pk_url_kwarg = "shoppinglist_id" @@ -327,26 +443,44 @@ class StockUpdateAfterShopppingBaseFormView(CounterAdminTabsMixin, CanEditMixin, def get_form_class(self): fields = OrderedDict() kwargs = {} - for t in ProductType.objects.order_by('name').all(): - for i in self.shoppinglist.shopping_items_to_buy.filter(type=t).order_by('name').all(): + for t in ProductType.objects.order_by("name").all(): + for i in ( + self.shoppinglist.shopping_items_to_buy.filter(type=t) + .order_by("name") + .all() + ): field_name = "item-%s" % (str(i.id)) - fields[field_name] = forms.CharField(max_length=30, required=True, label=str(i), - help_text=_(str(i.tobuy_quantity) + " asked")) - kwargs['shoppinglist_id'] = self.shoppinglist.id - kwargs['base_fields'] = fields - return type('StockUpdateAfterShopppingForm', (StockUpdateAfterShopppingForm,), kwargs) + fields[field_name] = forms.CharField( + max_length=30, + required=True, + label=str(i), + help_text=_(str(i.tobuy_quantity) + " asked"), + ) + kwargs["shoppinglist_id"] = self.shoppinglist.id + kwargs["base_fields"] = fields + return type( + "StockUpdateAfterShopppingForm", (StockUpdateAfterShopppingForm,), kwargs + ) def get(self, request, *args, **kwargs): - self.shoppinglist = ShoppingList.objects.filter(id=self.kwargs['shoppinglist_id']).first() - return super(StockUpdateAfterShopppingBaseFormView, self).get(request, *args, **kwargs) + self.shoppinglist = ShoppingList.objects.filter( + id=self.kwargs["shoppinglist_id"] + ).first() + return super(StockUpdateAfterShopppingBaseFormView, self).get( + request, *args, **kwargs + ) def post(self, request, *args, **kwargs): """ Handle the many possibilities of the post request """ self.object = self.get_object() - self.shoppinglist = ShoppingList.objects.filter(id=self.kwargs['shoppinglist_id']).first() - return super(StockUpdateAfterShopppingBaseFormView, self).post(request, *args, **kwargs) + self.shoppinglist = ShoppingList.objects.filter( + id=self.kwargs["shoppinglist_id"] + ).first() + return super(StockUpdateAfterShopppingBaseFormView, self).post( + request, *args, **kwargs + ) def form_valid(self, form): """ @@ -355,37 +489,45 @@ class StockUpdateAfterShopppingBaseFormView(CounterAdminTabsMixin, CanEditMixin, return super(StockUpdateAfterShopppingBaseFormView, self).form_valid(form) def get_context_data(self, **kwargs): - kwargs = super(StockUpdateAfterShopppingBaseFormView, self).get_context_data(**kwargs) - if 'form' not in kwargs.keys(): - kwargs['form'] = self.get_form() - kwargs['shoppinglist'] = self.shoppinglist - kwargs['stock'] = self.shoppinglist.stock_owner + kwargs = super(StockUpdateAfterShopppingBaseFormView, self).get_context_data( + **kwargs + ) + if "form" not in kwargs.keys(): + kwargs["form"] = self.get_form() + kwargs["shoppinglist"] = self.shoppinglist + kwargs["stock"] = self.shoppinglist.stock_owner return kwargs def get_success_url(self): - self.kwargs.pop('shoppinglist_id', None) - return reverse_lazy('stock:shoppinglist_list', args=self.args, kwargs=self.kwargs) + self.kwargs.pop("shoppinglist_id", None) + return reverse_lazy( + "stock:shoppinglist_list", args=self.args, kwargs=self.kwargs + ) class StockTakeItemsForm(forms.BaseForm): """ docstring for StockTakeItemsFormView """ + def clean(self): with transaction.atomic(): - for k,t in self.cleaned_data.items(): + for k, t in self.cleaned_data.items(): item_id = int(k[5:]) - if t > 0 : + if t > 0: item = StockItem.objects.filter(id=item_id).first() item.effective_quantity -= t item.save() return self.cleaned_data -class StockTakeItemsBaseFormView(CounterTabsMixin, CanEditMixin, DetailView, BaseFormView): +class StockTakeItemsBaseFormView( + CounterTabsMixin, CanEditMixin, DetailView, BaseFormView +): """ docstring for StockTakeItemsBaseFormView """ + model = StockItem template_name = "stock/stock_take_items.jinja" pk_url_kwarg = "stock_id" @@ -394,22 +536,31 @@ class StockTakeItemsBaseFormView(CounterTabsMixin, CanEditMixin, DetailView, Bas def get_form_class(self): fields = OrderedDict() kwargs = {} - for t in ProductType.objects.order_by('name').all(): - for i in self.stock.items.filter(type=t).order_by('name').all(): + for t in ProductType.objects.order_by("name").all(): + for i in self.stock.items.filter(type=t).order_by("name").all(): field_name = "item-%s" % (str(i.id)) - fields[field_name] = forms.IntegerField(required=False, label=str(i), initial=0, min_value=0, max_value=i.effective_quantity, - help_text=_("%(effective_quantity)s left" % {"effective_quantity": str(i.effective_quantity)})) + fields[field_name] = forms.IntegerField( + required=False, + label=str(i), + initial=0, + min_value=0, + max_value=i.effective_quantity, + help_text=_( + "%(effective_quantity)s left" + % {"effective_quantity": str(i.effective_quantity)} + ), + ) kwargs[field_name] = i.effective_quantity - kwargs['stock_id'] = self.stock.id - kwargs['counter_id'] = self.stock.counter.id - kwargs['base_fields'] = fields - return type('StockTakeItemsForm', (StockTakeItemsForm,), kwargs) + kwargs["stock_id"] = self.stock.id + kwargs["counter_id"] = self.stock.counter.id + kwargs["base_fields"] = fields + return type("StockTakeItemsForm", (StockTakeItemsForm,), kwargs) def get(self, request, *args, **kwargs): """ Simple get view """ - self.stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() + self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return super(StockTakeItemsBaseFormView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): @@ -417,11 +568,19 @@ class StockTakeItemsBaseFormView(CounterTabsMixin, CanEditMixin, DetailView, Bas Handle the many possibilities of the post request """ self.object = self.get_object() - self.stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() - if self.stock.counter.type == "BAR" and not ('counter_token' in self.request.session.keys() and - self.request.session['counter_token'] == self.stock.counter.token): # Also check the token to avoid the bar to be stolen - return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, - kwargs={'counter_id': self.stock.counter.id})+'?bad_location') + self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() + if self.stock.counter.type == "BAR" and not ( + "counter_token" in self.request.session.keys() + and self.request.session["counter_token"] == self.stock.counter.token + ): # Also check the token to avoid the bar to be stolen + return HttpResponseRedirect( + reverse_lazy( + "counter:details", + args=self.args, + kwargs={"counter_id": self.stock.counter.id}, + ) + + "?bad_location" + ) return super(StockTakeItemsBaseFormView, self).post(request, *args, **kwargs) def form_valid(self, form): @@ -429,14 +588,14 @@ class StockTakeItemsBaseFormView(CounterTabsMixin, CanEditMixin, DetailView, Bas def get_context_data(self, **kwargs): kwargs = super(StockTakeItemsBaseFormView, self).get_context_data(**kwargs) - if 'form' not in kwargs.keys(): - kwargs['form'] = self.get_form() - kwargs['stock'] = self.stock - kwargs['counter'] = self.stock.counter + if "form" not in kwargs.keys(): + kwargs["form"] = self.get_form() + kwargs["stock"] = self.stock + kwargs["counter"] = self.stock.counter return kwargs def get_success_url(self): - stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() - self.kwargs['counter_id'] = stock.counter.id - self.kwargs.pop('stock_id', None) - return reverse_lazy('counter:details', args=self.args, kwargs=self.kwargs) + stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() + self.kwargs["counter_id"] = stock.counter.id + self.kwargs.pop("stock_id", None) + return reverse_lazy("counter:details", args=self.args, kwargs=self.kwargs) diff --git a/subscription/__init__.py b/subscription/__init__.py index 0a9419f8..0ace29c4 100644 --- a/subscription/__init__.py +++ b/subscription/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/subscription/admin.py b/subscription/admin.py index 16e2be40..ba96e1d4 100644 --- a/subscription/admin.py +++ b/subscription/admin.py @@ -29,7 +29,12 @@ from haystack.admin import SearchModelAdmin class SubscriptionAdmin(SearchModelAdmin): - search_fields = ["member__username", "subscription_start", "subscription_end", "subscription_type"] + search_fields = [ + "member__username", + "subscription_start", + "subscription_end", + "subscription_type", + ] admin.site.register(Subscription, SubscriptionAdmin) diff --git a/subscription/migrations/0001_initial.py b/subscription/migrations/0001_initial.py index a34d5b66..2be00065 100644 --- a/subscription/migrations/0001_initial.py +++ b/subscription/migrations/0001_initial.py @@ -7,28 +7,78 @@ import django.contrib.auth.models class Migration(migrations.Migration): - dependencies = [ - ('core', '0001_initial'), - ] + dependencies = [("core", "0001_initial")] operations = [ migrations.CreateModel( - name='Subscription', + name="Subscription", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('subscription_type', models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('crous', 'CROUS member'), ('cursus-alternant', 'Branch cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('un-semestre', 'One semester')], max_length=255, verbose_name='subscription type')), - ('subscription_start', models.DateField(verbose_name='subscription start')), - ('subscription_end', models.DateField(verbose_name='subscription end')), - ('payment_method', models.CharField(choices=[('CHECK', 'Check'), ('CARD', 'Credit card'), ('CASH', 'Cash'), ('EBOUTIC', 'Eboutic'), ('OTHER', 'Other')], max_length=255, verbose_name='payment method')), - ('location', models.CharField(choices=[('BELFORT', 'Belfort'), ('SEVENANS', 'Sevenans'), ('MONTBELIARD', 'Montbéliard')], max_length=20, verbose_name='location')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "subscription_type", + models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("crous", "CROUS member"), + ("cursus-alternant", "Branch cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("un-semestre", "One semester"), + ], + max_length=255, + verbose_name="subscription type", + ), + ), + ( + "subscription_start", + models.DateField(verbose_name="subscription start"), + ), + ("subscription_end", models.DateField(verbose_name="subscription end")), + ( + "payment_method", + models.CharField( + choices=[ + ("CHECK", "Check"), + ("CARD", "Credit card"), + ("CASH", "Cash"), + ("EBOUTIC", "Eboutic"), + ("OTHER", "Other"), + ], + max_length=255, + verbose_name="payment method", + ), + ), + ( + "location", + models.CharField( + choices=[ + ("BELFORT", "Belfort"), + ("SEVENANS", "Sevenans"), + ("MONTBELIARD", "Montbéliard"), + ], + max_length=20, + verbose_name="location", + ), + ), ], - options={ - 'ordering': ['subscription_start'], - }, + options={"ordering": ["subscription_start"]}, ), migrations.AddField( - model_name='subscription', - name='member', - field=models.ForeignKey(to='core.User', related_name='subscriptions'), + model_name="subscription", + name="member", + field=models.ForeignKey(to="core.User", related_name="subscriptions"), ), ] diff --git a/subscription/migrations/0002_auto_20160830_1719.py b/subscription/migrations/0002_auto_20160830_1719.py index 13605bda..a649671d 100644 --- a/subscription/migrations/0002_auto_20160830_1719.py +++ b/subscription/migrations/0002_auto_20160830_1719.py @@ -6,14 +6,21 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0001_initial'), - ] + dependencies = [("subscription", "0001_initial")] operations = [ migrations.AlterField( - model_name='subscription', - name='location', - field=models.CharField(max_length=20, verbose_name='location', choices=[('BELFORT', 'Belfort'), ('SEVENANS', 'Sevenans'), ('MONTBELIARD', 'Montbéliard'), ('EBOUTIC', 'Eboutic')]), - ), + model_name="subscription", + name="location", + field=models.CharField( + max_length=20, + verbose_name="location", + choices=[ + ("BELFORT", "Belfort"), + ("SEVENANS", "Sevenans"), + ("MONTBELIARD", "Montbéliard"), + ("EBOUTIC", "Eboutic"), + ], + ), + ) ] diff --git a/subscription/migrations/0003_auto_20160902_1914.py b/subscription/migrations/0003_auto_20160902_1914.py index d2a6e41c..d1e23fd3 100644 --- a/subscription/migrations/0003_auto_20160902_1914.py +++ b/subscription/migrations/0003_auto_20160902_1914.py @@ -6,14 +6,28 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0002_auto_20160830_1719'), - ] + dependencies = [("subscription", "0002_auto_20160830_1719")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(max_length=255, choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('un-semestre', 'One semester')], verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + max_length=255, + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("un-semestre", "One semester"), + ], + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/migrations/0004_auto_20170821_1849.py b/subscription/migrations/0004_auto_20170821_1849.py index c0377bda..dc2ccdae 100644 --- a/subscription/migrations/0004_auto_20170821_1849.py +++ b/subscription/migrations/0004_auto_20170821_1849.py @@ -6,14 +6,30 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0003_auto_20160902_1914'), - ] + dependencies = [("subscription", "0003_auto_20160902_1914")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(verbose_name='subscription type', choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('sixieme-de-semestre', 'One month for free'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + verbose_name="subscription type", + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("sixieme-de-semestre", "One month for free"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + ), + ) ] diff --git a/subscription/migrations/0005_auto_20170821_2054.py b/subscription/migrations/0005_auto_20170821_2054.py index 660bc97f..291e806c 100644 --- a/subscription/migrations/0005_auto_20170821_2054.py +++ b/subscription/migrations/0005_auto_20170821_2054.py @@ -6,14 +6,29 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0004_auto_20170821_1849'), - ] + dependencies = [("subscription", "0004_auto_20170821_1849")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255, verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/migrations/0006_auto_20170902_1222.py b/subscription/migrations/0006_auto_20170902_1222.py index 2fd26aa1..e08d9dd3 100644 --- a/subscription/migrations/0006_auto_20170902_1222.py +++ b/subscription/migrations/0006_auto_20170902_1222.py @@ -6,14 +6,30 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0005_auto_20170821_2054'), - ] + dependencies = [("subscription", "0005_auto_20170821_2054")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(verbose_name='subscription type', choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-mois-essai', 'Two month for free'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + verbose_name="subscription type", + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-mois-essai", "Two month for free"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + ), + ) ] diff --git a/subscription/migrations/0007_auto_20180706_1135.py b/subscription/migrations/0007_auto_20180706_1135.py index 7ddc540d..7eab1a84 100644 --- a/subscription/migrations/0007_auto_20180706_1135.py +++ b/subscription/migrations/0007_auto_20180706_1135.py @@ -7,14 +7,31 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0006_auto_20170902_1222'), - ] + dependencies = [("subscription", "0006_auto_20170902_1222")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('benevoles-euroks', "Eurok's volunteer"), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-mois-essai', 'Two month for free'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255, verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("benevoles-euroks", "Eurok's volunteer"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-mois-essai", "Two month for free"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/migrations/0008_auto_20180831_2016.py b/subscription/migrations/0008_auto_20180831_2016.py index fd7b4654..8582c835 100644 --- a/subscription/migrations/0008_auto_20180831_2016.py +++ b/subscription/migrations/0008_auto_20180831_2016.py @@ -7,14 +7,32 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0007_auto_20180706_1135'), - ] + dependencies = [("subscription", "0007_auto_20180706_1135")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('benevoles-euroks', "Eurok's volunteer"), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-mois-essai', 'Two month for free'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('six-semaines-essai', 'Six weeks for free'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255, verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("benevoles-euroks", "Eurok's volunteer"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-mois-essai", "Two month for free"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("six-semaines-essai", "Six weeks for free"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/migrations/0009_auto_20180920_1421.py b/subscription/migrations/0009_auto_20180920_1421.py index 4d975dcd..a4330cd2 100644 --- a/subscription/migrations/0009_auto_20180920_1421.py +++ b/subscription/migrations/0009_auto_20180920_1421.py @@ -7,14 +7,33 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0008_auto_20180831_2016'), - ] + dependencies = [("subscription", "0008_auto_20180831_2016")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('benevoles-euroks', "Eurok's volunteer"), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-mois-essai', 'Two months for free'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('six-semaines-essai', 'Six weeks for free'), ('un-jour', 'Un jour'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255, verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("benevoles-euroks", "Eurok's volunteer"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-mois-essai", "Two months for free"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("six-semaines-essai", "Six weeks for free"), + ("un-jour", "Un jour"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/migrations/0010_auto_20180920_1441.py b/subscription/migrations/0010_auto_20180920_1441.py index 97b879ad..af14dcd4 100644 --- a/subscription/migrations/0010_auto_20180920_1441.py +++ b/subscription/migrations/0010_auto_20180920_1441.py @@ -7,14 +7,33 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0009_auto_20180920_1421'), - ] + dependencies = [("subscription", "0009_auto_20180920_1421")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('benevoles-euroks', "Eurok's volunteer"), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-mois-essai', 'Two months for free'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('six-semaines-essai', 'Six weeks for free'), ('un-jour', 'One day'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255, verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("benevoles-euroks", "Eurok's volunteer"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-mois-essai", "Two months for free"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("six-semaines-essai", "Six weeks for free"), + ("un-jour", "One day"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/models.py b/subscription/models.py index 853d2dbc..21bc91b3 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -41,74 +41,101 @@ from core.utils import get_start_of_semester def validate_type(value): if value not in settings.SITH_SUBSCRIPTIONS.keys(): - raise ValidationError(_('Bad subscription type')) + raise ValidationError(_("Bad subscription type")) def validate_payment(value): if value not in settings.SITH_SUBSCRIPTION_PAYMENT_METHOD: - raise ValidationError(_('Bad payment method')) + raise ValidationError(_("Bad payment method")) class Subscription(models.Model): - member = models.ForeignKey(User, related_name='subscriptions') - subscription_type = models.CharField(_('subscription type'), - max_length=255, - choices=((k, v['name']) for k, v in sorted(settings.SITH_SUBSCRIPTIONS.items()))) - subscription_start = models.DateField(_('subscription start')) - subscription_end = models.DateField(_('subscription end')) - payment_method = models.CharField(_('payment method'), - max_length=255, - choices=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD) - location = models.CharField(choices=settings.SITH_SUBSCRIPTION_LOCATIONS, - max_length=20, verbose_name=_('location')) + member = models.ForeignKey(User, related_name="subscriptions") + subscription_type = models.CharField( + _("subscription type"), + max_length=255, + choices=( + (k, v["name"]) for k, v in sorted(settings.SITH_SUBSCRIPTIONS.items()) + ), + ) + subscription_start = models.DateField(_("subscription start")) + subscription_end = models.DateField(_("subscription end")) + payment_method = models.CharField( + _("payment method"), + max_length=255, + choices=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD, + ) + location = models.CharField( + choices=settings.SITH_SUBSCRIPTION_LOCATIONS, + max_length=20, + verbose_name=_("location"), + ) class Meta: - ordering = ['subscription_start', ] + ordering = ["subscription_start"] def clean(self): try: - for s in Subscription.objects.filter(member=self.member).exclude(pk=self.pk).all(): - if s.is_valid_now() and s.subscription_end - timedelta(weeks=settings.SITH_SUBSCRIPTION_END) > date.today(): - raise ValidationError(_("You can not subscribe many time for the same period")) + for s in ( + Subscription.objects.filter(member=self.member) + .exclude(pk=self.pk) + .all() + ): + if ( + s.is_valid_now() + and s.subscription_end + - timedelta(weeks=settings.SITH_SUBSCRIPTION_END) + > date.today() + ): + raise ValidationError( + _("You can not subscribe many time for the same period") + ) except: # This should not happen, because the form should have handled the data before, but sadly, it still - # calls the model validation :'( - # TODO see SubscriptionForm's clean method + # calls the model validation :'( + # TODO see SubscriptionForm's clean method raise ValidationError(_("Subscription error")) def save(self): super(Subscription, self).save() from counter.models import Customer + if not Customer.objects.filter(user=self.member).exists(): - last_id = Customer.objects.count() + 1504 # Number to keep a continuity with the old site - Customer(user=self.member, account_id=Customer.generate_account_id(last_id + 1), amount=0).save() - form = PasswordResetForm({'email': self.member.email}) + last_id = ( + Customer.objects.count() + 1504 + ) # Number to keep a continuity with the old site + Customer( + user=self.member, + account_id=Customer.generate_account_id(last_id + 1), + amount=0, + ).save() + form = PasswordResetForm({"email": self.member.email}) if form.is_valid(): - form.save(use_https=True, email_template_name='core/new_user_email.jinja', - subject_template_name='core/new_user_email_subject.jinja', from_email="ae@utbm.fr") + form.save( + use_https=True, + email_template_name="core/new_user_email.jinja", + subject_template_name="core/new_user_email_subject.jinja", + from_email="ae@utbm.fr", + ) self.member.make_home() if settings.IS_OLD_MYSQL_PRESENT: import MySQLdb + try: # Create subscription on the old site: TODO remove me! - LOCATION = { - "SEVENANS": 5, - "BELFORT": 6, - "MONTBELIARD": 9, - "EBOUTIC": 5, - } + LOCATION = {"SEVENANS": 5, "BELFORT": 6, "MONTBELIARD": 9, "EBOUTIC": 5} TYPE = { - 'un-semestre': 0, - 'deux-semestres': 1, - 'cursus-tronc-commun': 2, - 'cursus-branche': 3, - 'membre-honoraire': 4, - 'assidu': 5, - 'amicale/doceo': 6, - 'reseau-ut': 7, - 'crous': 8, - 'sbarro/esta': 9, - 'cursus-alternant': 10, - 'welcome-semestre': 11, - 'deux-mois-essai': 12, + "un-semestre": 0, + "deux-semestres": 1, + "cursus-tronc-commun": 2, + "cursus-branche": 3, + "membre-honoraire": 4, + "assidu": 5, + "amicale/doceo": 6, + "reseau-ut": 7, + "crous": 8, + "sbarro/esta": 9, + "cursus-alternant": 10, + "welcome-semestre": 11, + "deux-mois-essai": 12, } PAYMENT = { "CHECK": 1, @@ -121,25 +148,36 @@ class Subscription(models.Model): db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) c = db.cursor() - c.execute("""INSERT INTO ae_cotisations (id_utilisateur, date_cotis, date_fin_cotis, mode_paiement_cotis, - type_cotis, id_comptoir) VALUES (%s, %s, %s, %s, %s, %s)""", (self.member.id, self.subscription_start, - self.subscription_end, PAYMENT[self.payment_method], TYPE[self.subscription_type], - LOCATION[self.location])) + c.execute( + """INSERT INTO ae_cotisations (id_utilisateur, date_cotis, date_fin_cotis, mode_paiement_cotis, + type_cotis, id_comptoir) VALUES (%s, %s, %s, %s, %s, %s)""", + ( + self.member.id, + self.subscription_start, + self.subscription_end, + PAYMENT[self.payment_method], + TYPE[self.subscription_type], + LOCATION[self.location], + ), + ) db.commit() except Exception as e: with open(settings.BASE_DIR + "/subscription_fail.log", "a") as f: - print("FAIL to add subscription to %s to old site" % (self.member), file=f) + print( + "FAIL to add subscription to %s to old site" % (self.member), + file=f, + ) print("Reason: %s" % (repr(e)), file=f) db.rollback() def get_absolute_url(self): - return reverse('core:user_edit', kwargs={'user_id': self.member.pk}) + return reverse("core:user_edit", kwargs={"user_id": self.member.pk}) def __str__(self): if hasattr(self, "member") and self.member is not None: - return self.member.username + ' - ' + str(self.pk) + return self.member.username + " - " + str(self.pk) else: - return 'No user - ' + str(self.pk) + return "No user - " + str(self.pk) @staticmethod def compute_start(d=None, duration=1, user=None): @@ -176,21 +214,43 @@ class Subscription(models.Model): if start is None: start = Subscription.compute_start(duration=duration, user=user) - return start + relativedelta(months=round(6*duration),days=math.ceil((6*duration - round(6*duration)) * 30)) - + return start + relativedelta( + months=round(6 * duration), + days=math.ceil((6 * duration - round(6 * duration)) * 30), + ) + def can_be_edited_by(self, user): return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root def is_valid_now(self): - return self.subscription_start <= date.today() and date.today() <= self.subscription_end + return ( + self.subscription_start <= date.today() + and date.today() <= self.subscription_end + ) def guy_test(date, duration=4): - print(str(date) + " - " + str(duration) + " -> " + str(Subscription.compute_start(date, duration))) + print( + str(date) + + " - " + + str(duration) + + " -> " + + str(Subscription.compute_start(date, duration)) + ) def bibou_test(duration, date=date.today()): - print(str(date) + " - " + str(duration) + " -> " + str(Subscription.compute_end(duration, Subscription.compute_start(date, duration)))) + print( + str(date) + + " - " + + str(duration) + + " -> " + + str( + Subscription.compute_end( + duration, Subscription.compute_start(date, duration) + ) + ) + ) def guy(): @@ -202,7 +262,7 @@ def guy(): guy_test(date(2015, 2, 11)) guy_test(date(2015, 8, 17)) guy_test(date(2015, 9, 17)) - print('=' * 80) + print("=" * 80) guy_test(date(2015, 7, 11), 1) guy_test(date(2015, 8, 11), 2) guy_test(date(2015, 2, 17), 3) @@ -211,7 +271,7 @@ def guy(): guy_test(date(2015, 2, 11), 2) guy_test(date(2015, 8, 17), 3) guy_test(date(2015, 9, 17), 4) - print('=' * 80) + print("=" * 80) bibou_test(1, date(2015, 2, 18)) bibou_test(2, date(2015, 2, 18)) bibou_test(3, date(2015, 2, 18)) @@ -220,7 +280,7 @@ def guy(): bibou_test(2, date(2015, 9, 18)) bibou_test(3, date(2015, 9, 18)) bibou_test(4, date(2015, 9, 18)) - print('=' * 80) + print("=" * 80) bibou_test(1, date(2000, 2, 29)) bibou_test(2, date(2000, 2, 29)) bibou_test(1, date(2000, 5, 31)) diff --git a/subscription/tests.py b/subscription/tests.py index 6003df62..895e6596 100644 --- a/subscription/tests.py +++ b/subscription/tests.py @@ -34,6 +34,7 @@ from django.core.management import call_command class FakeDate(date): """A fake replacement for date that can be mocked for testing.""" + def __new__(cls, *args, **kwargs): return date.__new__(date, *args, **kwargs) @@ -43,8 +44,7 @@ def date_mock_today(year, month, day): class SubscriptionUnitTest(TestCase): - - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_start_dates_sliding_without_start(self): date_mock_today(2015, 9, 18) d = Subscription.compute_start(duration=1) @@ -52,12 +52,14 @@ class SubscriptionUnitTest(TestCase): self.assertTrue(Subscription.compute_start(duration=2) == date(2015, 9, 18)) def test_start_dates_sliding_with_start(self): - self.assertTrue(Subscription.compute_start(date(2015, 5, 17), 1) == - date(2015, 5, 17)) - self.assertTrue(Subscription.compute_start(date(2015, 5, 17), 2) == - date(2015, 5, 17)) + self.assertTrue( + Subscription.compute_start(date(2015, 5, 17), 1) == date(2015, 5, 17) + ) + self.assertTrue( + Subscription.compute_start(date(2015, 5, 17), 2) == date(2015, 5, 17) + ) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_start_dates_not_sliding_without_start(self): date_mock_today(2015, 5, 17) self.assertTrue(Subscription.compute_start(duration=3) == date(2015, 2, 15)) @@ -67,10 +69,14 @@ class SubscriptionUnitTest(TestCase): self.assertTrue(Subscription.compute_start(duration=4) == date(2015, 8, 15)) def test_start_dates_not_sliding_with_start(self): - self.assertTrue(Subscription.compute_start(date(2015, 5, 17), 3) == date(2015, 2, 15)) - self.assertTrue(Subscription.compute_start(date(2015, 1, 11), 3) == date(2014, 8, 15)) + self.assertTrue( + Subscription.compute_start(date(2015, 5, 17), 3) == date(2015, 2, 15) + ) + self.assertTrue( + Subscription.compute_start(date(2015, 1, 11), 3) == date(2014, 8, 15) + ) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_end_dates_sliding(self): date_mock_today(2015, 9, 18) d = Subscription.compute_end(2) @@ -78,7 +84,7 @@ class SubscriptionUnitTest(TestCase): d = Subscription.compute_end(1) self.assertTrue(d == date(2016, 3, 18)) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_end_dates_not_sliding_without_start(self): date_mock_today(2015, 9, 18) d = Subscription.compute_end(duration=3) @@ -86,7 +92,7 @@ class SubscriptionUnitTest(TestCase): d = Subscription.compute_end(duration=4) self.assertTrue(d == date(2017, 8, 15)) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_end_dates_with_float(self): date_mock_today(2015, 9, 18) d = Subscription.compute_end(duration=0.33) @@ -102,6 +108,7 @@ class SubscriptionUnitTest(TestCase): d = Subscription.compute_end(duration=4, start=date(2015, 9, 18)) self.assertTrue(d == date(2017, 9, 18)) + class SubscriptionIntegrationTest(TestCase): def setUp(self): call_command("populate") @@ -109,98 +116,130 @@ class SubscriptionIntegrationTest(TestCase): def test_duration_two_months(self): - s = Subscription(member=User.objects.filter(pk=self.user.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=self.user.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2017, 8, 29) - s.subscription_end = s.compute_end(duration=0.33, - start=s.subscription_start) + s.subscription_end = s.compute_end(duration=0.33, start=s.subscription_start) s.save() self.assertTrue(s.subscription_end == date(2017, 10, 29)) def test_duration_two_months(self): - s = Subscription(member=User.objects.filter(pk=self.user.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=self.user.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2017, 8, 29) - s.subscription_end = s.compute_end(duration=settings.SITH_SUBSCRIPTIONS['un-jour']['duration'], - start=s.subscription_start) + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["un-jour"]["duration"], + start=s.subscription_start, + ) s.save() self.assertTrue(s.subscription_end == date(2017, 8, 30)) def test_duration_three_months(self): - s = Subscription(member=User.objects.filter(pk=self.user.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=self.user.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2017, 8, 29) - s.subscription_end = s.compute_end(duration=0.5, - start=s.subscription_start) + s.subscription_end = s.compute_end(duration=0.5, start=s.subscription_start) s.save() self.assertTrue(s.subscription_end == date(2017, 11, 29)) def test_duration_four_months(self): - s = Subscription(member=User.objects.filter(pk=self.user.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=self.user.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2017, 8, 29) - s.subscription_end = s.compute_end(duration=0.67, - start=s.subscription_start) + s.subscription_end = s.compute_end(duration=0.67, start=s.subscription_start) s.save() self.assertTrue(s.subscription_end == date(2017, 12, 30)) - + def test_duration_six_weeks(self): - s = Subscription(member=User.objects.filter(pk=self.user.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=self.user.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2018, 9, 1) - s.subscription_end = s.compute_end(duration=0.23, - start=s.subscription_start) + s.subscription_end = s.compute_end(duration=0.23, start=s.subscription_start) s.save() self.assertTrue(s.subscription_end == date(2018, 10, 13)) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_dates_sliding_with_subscribed_user(self): user = User.objects.filter(pk=self.user.pk).first() - s = Subscription(member=user, subscription_type='deux-semestres', - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=user, + subscription_type="deux-semestres", + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2015, 8, 29) - s.subscription_end = s.compute_end(duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() self.assertTrue(s.subscription_end == date(2016, 8, 29)) date_mock_today(2016, 8, 25) - d = Subscription.compute_end(duration=settings.SITH_SUBSCRIPTIONS['deux-semestres']['duration'], - user=user) - + d = Subscription.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], + user=user, + ) self.assertTrue(d == date(2017, 8, 29)) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_dates_renewal_sliding_during_two_free_monthes(self): user = User.objects.filter(pk=self.user.pk).first() - s = Subscription(member=user, subscription_type='deux-mois-essai', - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=user, + subscription_type="deux-mois-essai", + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2015, 8, 29) - s.subscription_end = s.compute_end(duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() self.assertTrue(s.subscription_end == date(2015, 10, 29)) date_mock_today(2015, 9, 25) - d = Subscription.compute_end(duration=settings.SITH_SUBSCRIPTIONS['deux-semestres']['duration'], - user=user) + d = Subscription.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], + user=user, + ) self.assertTrue(d == date(2016, 10, 29)) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_dates_renewal_sliding_after_two_free_monthes(self): user = User.objects.filter(pk=self.user.pk).first() - s = Subscription(member=user, subscription_type='deux-mois-essai', - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=user, + subscription_type="deux-mois-essai", + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2015, 8, 29) - s.subscription_end = s.compute_end(duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() self.assertTrue(s.subscription_end == date(2015, 10, 29)) date_mock_today(2015, 11, 5) - d = Subscription.compute_end(duration=settings.SITH_SUBSCRIPTIONS['deux-semestres']['duration'], - user=user) + d = Subscription.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], + user=user, + ) self.assertTrue(d == date(2016, 11, 5)) - diff --git a/subscription/urls.py b/subscription/urls.py index 4d31199d..e271c0e1 100644 --- a/subscription/urls.py +++ b/subscription/urls.py @@ -28,6 +28,6 @@ from subscription.views import * urlpatterns = [ # Subscription views - url(r'^$', NewSubscription.as_view(), name='subscription'), - url(r'stats', SubscriptionsStatsView.as_view(), name='stats'), + url(r"^$", NewSubscription.as_view(), name="subscription"), + url(r"stats", SubscriptionsStatsView.as_view(), name="stats"), ] diff --git a/subscription/views.py b/subscription/views.py index a2e5f7a8..81e1c921 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -40,29 +40,40 @@ from core.models import User class SelectionDateForm(forms.Form): def __init__(self, *args, **kwargs): super(SelectionDateForm, self).__init__(*args, **kwargs) - self.fields['start_date'] = forms.DateTimeField( - ['%Y-%m-%d %H:%M:%S'], label=_("Start date"), - widget=SelectDateTime, required=True) - self.fields['end_date'] = forms.DateTimeField( - ['%Y-%m-%d %H:%M:%S'], label=_("End date"), - widget=SelectDateTime, required=True) + self.fields["start_date"] = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Start date"), + widget=SelectDateTime, + required=True, + ) + self.fields["end_date"] = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End date"), + widget=SelectDateTime, + required=True, + ) class SubscriptionForm(forms.ModelForm): class Meta: model = Subscription - fields = ['member', 'subscription_type', 'payment_method', 'location'] - member = AutoCompleteSelectField('users', required=False, help_text=None) + fields = ["member", "subscription_type", "payment_method", "location"] + + member = AutoCompleteSelectField("users", required=False, help_text=None) def __init__(self, *args, **kwargs): super(SubscriptionForm, self).__init__(*args, **kwargs) # Add fields to allow basic user creation - self.fields['last_name'] = forms.CharField(max_length=User._meta.get_field('last_name').max_length) - self.fields['first_name'] = forms.CharField(max_length=User._meta.get_field('first_name').max_length) - self.fields['email'] = forms.EmailField() - self.fields.move_to_end('subscription_type') - self.fields.move_to_end('payment_method') - self.fields.move_to_end('location') + self.fields["last_name"] = forms.CharField( + max_length=User._meta.get_field("last_name").max_length + ) + self.fields["first_name"] = forms.CharField( + max_length=User._meta.get_field("first_name").max_length + ) + self.fields["email"] = forms.EmailField() + self.fields.move_to_end("subscription_type") + self.fields.move_to_end("payment_method") + self.fields.move_to_end("location") def clean_member(self): subscriber = self.cleaned_data.get("member") @@ -72,19 +83,26 @@ class SubscriptionForm(forms.ModelForm): def clean(self): cleaned_data = super(SubscriptionForm, self).clean() - if (cleaned_data.get("member") is None - and "last_name" not in self.errors.as_data() - and "first_name" not in self.errors.as_data() - and "email" not in self.errors.as_data()): + if ( + cleaned_data.get("member") is None + and "last_name" not in self.errors.as_data() + and "first_name" not in self.errors.as_data() + and "email" not in self.errors.as_data() + ): self.errors.pop("member", None) if self.errors: return cleaned_data if User.objects.filter(email=cleaned_data.get("email")).first() is not None: - self.add_error("email", ValidationError(_("A user with that email address already exists"))) + self.add_error( + "email", + ValidationError(_("A user with that email address already exists")), + ) else: - u = User(last_name=self.cleaned_data.get("last_name"), - first_name=self.cleaned_data.get("first_name"), - email=self.cleaned_data.get("email")) + u = User( + last_name=self.cleaned_data.get("last_name"), + first_name=self.cleaned_data.get("first_name"), + email=self.cleaned_data.get("email"), + ) u.generate_username() u.set_password(str(random.randrange(1000000, 10000000))) u.save() @@ -96,12 +114,16 @@ class SubscriptionForm(forms.ModelForm): if cleaned_data.get("member") is None: # This should be handled here, but it is done in the Subscription model's clean method # TODO investigate why! - raise ValidationError(_("You must either choose an existing user or create a new one properly")) + raise ValidationError( + _( + "You must either choose an existing user or create a new one properly" + ) + ) return cleaned_data class NewSubscription(CreateView): - template_name = 'subscription/subscription.jinja' + template_name = "subscription/subscription.jinja" form_class = SubscriptionForm def dispatch(self, request, *arg, **kwargs): @@ -111,18 +133,26 @@ class NewSubscription(CreateView): raise PermissionDenied def get_initial(self): - if 'member' in self.request.GET.keys(): - return {'member': self.request.GET['member'], 'subscription_type': 'deux-semestres'} - return {'subscription_type': 'deux-semestres'} + if "member" in self.request.GET.keys(): + return { + "member": self.request.GET["member"], + "subscription_type": "deux-semestres", + } + return {"subscription_type": "deux-semestres"} def form_valid(self, form): form.instance.subscription_start = Subscription.compute_start( - duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'], - user=form.instance.member) + duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type][ + "duration" + ], + user=form.instance.member, + ) form.instance.subscription_end = Subscription.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'], + duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type][ + "duration" + ], start=form.instance.subscription_start, - user=form.instance.member + user=form.instance.member, ) return super(NewSubscription, self).form_valid(form) @@ -133,41 +163,41 @@ class SubscriptionsStatsView(FormView): def dispatch(self, request, *arg, **kwargs): import datetime + self.start_date = datetime.datetime.today() self.end_date = self.start_date - res = super(SubscriptionsStatsView, self).dispatch( - request, *arg, **kwargs) + res = super(SubscriptionsStatsView, self).dispatch(request, *arg, **kwargs) if request.user.is_root or request.user.is_board_member: return res raise PermissionDenied def post(self, request, *args, **kwargs): self.form = self.get_form() - self.start_date = self.form['start_date'] - self.end_date = self.form['end_date'] - res = super(SubscriptionsStatsView, self).post( - request, *args, **kwargs) + self.start_date = self.form["start_date"] + self.end_date = self.form["end_date"] + res = super(SubscriptionsStatsView, self).post(request, *args, **kwargs) if request.user.is_root or request.user.is_board_member: return res raise PermissionDenied def get_initial(self): init = { - 'start_date': self.start_date.strftime('%Y-%m-%d %H:%M:%S'), - 'end_date': self.end_date.strftime('%Y-%m-%d %H:%M:%S') + "start_date": self.start_date.strftime("%Y-%m-%d %H:%M:%S"), + "end_date": self.end_date.strftime("%Y-%m-%d %H:%M:%S"), } return init def get_context_data(self, **kwargs): from subscription.models import Subscription + kwargs = super(SubscriptionsStatsView, self).get_context_data(**kwargs) - kwargs['subscriptions_total'] = Subscription.objects.filter( - subscription_end__gte=self.end_date, - subscription_start__lte=self.start_date) - kwargs['subscriptions_types'] = settings.SITH_SUBSCRIPTIONS - kwargs['payment_types'] = settings.SITH_COUNTER_PAYMENT_METHOD - kwargs['locations'] = settings.SITH_SUBSCRIPTION_LOCATIONS + kwargs["subscriptions_total"] = Subscription.objects.filter( + subscription_end__gte=self.end_date, subscription_start__lte=self.start_date + ) + kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS + kwargs["payment_types"] = settings.SITH_COUNTER_PAYMENT_METHOD + kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS return kwargs def get_success_url(self, **kwargs): - return reverse_lazy('subscriptions:stats') + return reverse_lazy("subscriptions:stats") diff --git a/trombi/__init__.py b/trombi/__init__.py index 55a358e2..e9ebba43 100644 --- a/trombi/__init__.py +++ b/trombi/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/trombi/migrations/0001_initial.py b/trombi/migrations/0001_initial.py index 8c1f9a67..4f850f3d 100644 --- a/trombi/migrations/0001_initial.py +++ b/trombi/migrations/0001_initial.py @@ -10,46 +10,134 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('club', '0007_auto_20170324_0917'), + ("club", "0007_auto_20170324_0917"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Trombi', + name="Trombi", fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ('subscription_deadline', models.DateField(default=datetime.date.today, help_text='Before this date, users are allowed to subscribe to this Trombi. After this date, users subscribed will be allowed to comment on each other.', verbose_name='subscription deadline')), - ('comments_deadline', models.DateField(default=datetime.date.today, help_text="After this date, users won't be able to make comments anymore.", verbose_name='comments deadline')), - ('max_chars', models.IntegerField(default=400, help_text='Maximum number of characters allowed in a comment.', verbose_name='maximum characters')), - ('club', models.OneToOneField(to='club.Club', related_name='trombi')), + ( + "id", + models.AutoField( + serialize=False, + auto_created=True, + verbose_name="ID", + primary_key=True, + ), + ), + ( + "subscription_deadline", + models.DateField( + default=datetime.date.today, + help_text="Before this date, users are allowed to subscribe to this Trombi. After this date, users subscribed will be allowed to comment on each other.", + verbose_name="subscription deadline", + ), + ), + ( + "comments_deadline", + models.DateField( + default=datetime.date.today, + help_text="After this date, users won't be able to make comments anymore.", + verbose_name="comments deadline", + ), + ), + ( + "max_chars", + models.IntegerField( + default=400, + help_text="Maximum number of characters allowed in a comment.", + verbose_name="maximum characters", + ), + ), + ("club", models.OneToOneField(to="club.Club", related_name="trombi")), ], ), migrations.CreateModel( - name='TrombiComment', + name="TrombiComment", fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ('content', models.TextField(default='', verbose_name='content')), + ( + "id", + models.AutoField( + serialize=False, + auto_created=True, + verbose_name="ID", + primary_key=True, + ), + ), + ("content", models.TextField(default="", verbose_name="content")), ], ), migrations.CreateModel( - name='TrombiUser', + name="TrombiUser", fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ('profile_pict', models.ImageField(upload_to='trombi', blank=True, help_text='The profile picture you want in the trombi (warning: this picture may be published)', verbose_name='profile pict', null=True)), - ('scrub_pict', models.ImageField(upload_to='trombi', blank=True, help_text='The scrub picture you want in the trombi (warning: this picture may be published)', verbose_name='scrub pict', null=True)), - ('trombi', models.ForeignKey(to='trombi.Trombi', blank=True, verbose_name='trombi', related_name='users', on_delete=django.db.models.deletion.SET_NULL, null=True)), - ('user', models.OneToOneField(verbose_name='trombi user', to=settings.AUTH_USER_MODEL, related_name='trombi_user')), + ( + "id", + models.AutoField( + serialize=False, + auto_created=True, + verbose_name="ID", + primary_key=True, + ), + ), + ( + "profile_pict", + models.ImageField( + upload_to="trombi", + blank=True, + help_text="The profile picture you want in the trombi (warning: this picture may be published)", + verbose_name="profile pict", + null=True, + ), + ), + ( + "scrub_pict", + models.ImageField( + upload_to="trombi", + blank=True, + help_text="The scrub picture you want in the trombi (warning: this picture may be published)", + verbose_name="scrub pict", + null=True, + ), + ), + ( + "trombi", + models.ForeignKey( + to="trombi.Trombi", + blank=True, + verbose_name="trombi", + related_name="users", + on_delete=django.db.models.deletion.SET_NULL, + null=True, + ), + ), + ( + "user", + models.OneToOneField( + verbose_name="trombi user", + to=settings.AUTH_USER_MODEL, + related_name="trombi_user", + ), + ), ], ), migrations.AddField( - model_name='trombicomment', - name='author', - field=models.ForeignKey(to='trombi.TrombiUser', verbose_name='author', related_name='given_comments'), + model_name="trombicomment", + name="author", + field=models.ForeignKey( + to="trombi.TrombiUser", + verbose_name="author", + related_name="given_comments", + ), ), migrations.AddField( - model_name='trombicomment', - name='target', - field=models.ForeignKey(to='trombi.TrombiUser', verbose_name='target', related_name='received_comments'), + model_name="trombicomment", + name="target", + field=models.ForeignKey( + to="trombi.TrombiUser", + verbose_name="target", + related_name="received_comments", + ), ), ] diff --git a/trombi/migrations/0002_trombi_show_profiles.py b/trombi/migrations/0002_trombi_show_profiles.py index 3cb2f508..a3977b94 100644 --- a/trombi/migrations/0002_trombi_show_profiles.py +++ b/trombi/migrations/0002_trombi_show_profiles.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('trombi', '0001_initial'), - ] + dependencies = [("trombi", "0001_initial")] operations = [ migrations.AddField( - model_name='trombi', - name='show_profiles', - field=models.BooleanField(default=True, verbose_name='show users profiles to each other'), - ), + model_name="trombi", + name="show_profiles", + field=models.BooleanField( + default=True, verbose_name="show users profiles to each other" + ), + ) ] diff --git a/trombi/migrations/0003_trombicomment_is_moderated.py b/trombi/migrations/0003_trombicomment_is_moderated.py index 9089b305..2c44bc61 100644 --- a/trombi/migrations/0003_trombicomment_is_moderated.py +++ b/trombi/migrations/0003_trombicomment_is_moderated.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('trombi', '0002_trombi_show_profiles'), - ] + dependencies = [("trombi", "0002_trombi_show_profiles")] operations = [ migrations.AddField( - model_name='trombicomment', - name='is_moderated', - field=models.BooleanField(default=False, verbose_name='is the comment moderated'), - ), + model_name="trombicomment", + name="is_moderated", + field=models.BooleanField( + default=False, verbose_name="is the comment moderated" + ), + ) ] diff --git a/trombi/migrations/0004_trombiclubmembership.py b/trombi/migrations/0004_trombiclubmembership.py index dc1c6e09..2f6d3023 100644 --- a/trombi/migrations/0004_trombiclubmembership.py +++ b/trombi/migrations/0004_trombiclubmembership.py @@ -6,23 +6,46 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('trombi', '0003_trombicomment_is_moderated'), - ] + dependencies = [("trombi", "0003_trombicomment_is_moderated")] operations = [ migrations.CreateModel( - name='TrombiClubMembership', + name="TrombiClubMembership", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('club', models.CharField(default='', max_length=32, verbose_name='club')), - ('role', models.CharField(default='', max_length=64, verbose_name='role')), - ('start', models.CharField(default='', max_length=16, verbose_name='start')), - ('end', models.CharField(default='', max_length=16, verbose_name='end')), - ('user', models.ForeignKey(verbose_name='user', related_name='memberships', to='trombi.TrombiUser')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "club", + models.CharField(default="", max_length=32, verbose_name="club"), + ), + ( + "role", + models.CharField(default="", max_length=64, verbose_name="role"), + ), + ( + "start", + models.CharField(default="", max_length=16, verbose_name="start"), + ), + ( + "end", + models.CharField(default="", max_length=16, verbose_name="end"), + ), + ( + "user", + models.ForeignKey( + verbose_name="user", + related_name="memberships", + to="trombi.TrombiUser", + ), + ), ], - options={ - 'ordering': ['id'], - }, - ), + options={"ordering": ["id"]}, + ) ] diff --git a/trombi/models.py b/trombi/models.py index c7533560..f60400de 100644 --- a/trombi/models.py +++ b/trombi/models.py @@ -42,8 +42,11 @@ class TrombiManager(models.Manager): class AvailableTrombiManager(models.Manager): def get_queryset(self): - return super(AvailableTrombiManager, - self).get_queryset().filter(subscription_deadline__gte=date.today()) + return ( + super(AvailableTrombiManager, self) + .get_queryset() + .filter(subscription_deadline__gte=date.today()) + ) class Trombi(models.Model): @@ -52,17 +55,32 @@ class Trombi(models.Model): It contains the deadlines for the users, and the link to the club that makes its Trombi. """ - subscription_deadline = models.DateField(_('subscription deadline'), - default=date.today, help_text=_("Before this date, users are " - "allowed to subscribe to this Trombi. " - "After this date, users subscribed will be allowed to comment on each other.")) - comments_deadline = models.DateField(_('comments deadline'), - default=date.today, help_text=_("After this date, users won't be " - "able to make comments anymore.")) - max_chars = models.IntegerField(_('maximum characters'), default=400, - help_text=_('Maximum number of characters allowed in a comment.')) - show_profiles = models.BooleanField(_("show users profiles to each other"), default=True) - club = models.OneToOneField(Club, related_name='trombi') + + subscription_deadline = models.DateField( + _("subscription deadline"), + default=date.today, + help_text=_( + "Before this date, users are " + "allowed to subscribe to this Trombi. " + "After this date, users subscribed will be allowed to comment on each other." + ), + ) + comments_deadline = models.DateField( + _("comments deadline"), + default=date.today, + help_text=_( + "After this date, users won't be " "able to make comments anymore." + ), + ) + max_chars = models.IntegerField( + _("maximum characters"), + default=400, + help_text=_("Maximum number of characters allowed in a comment."), + ) + show_profiles = models.BooleanField( + _("show users profiles to each other"), default=True + ) + club = models.OneToOneField(Club, related_name="trombi") objects = TrombiManager() availables = AvailableTrombiManager() @@ -72,11 +90,15 @@ class Trombi(models.Model): def clean(self): if self.subscription_deadline > self.comments_deadline: - raise ValidationError(_("Closing the subscriptions after the " - "comments is definitively not a good idea.")) + raise ValidationError( + _( + "Closing the subscriptions after the " + "comments is definitively not a good idea." + ) + ) def get_absolute_url(self): - return reverse('trombi:detail', kwargs={'trombi_id': self.id}) + return reverse("trombi:detail", kwargs={"trombi_id": self.id}) def is_owned_by(self, user): return user.can_edit(self.club) @@ -93,12 +115,36 @@ class TrombiUser(models.Model): It also adds the pictures to the profile without needing all the security like the other SithFiles. """ - user = models.OneToOneField(User, verbose_name=_("trombi user"), related_name='trombi_user') - trombi = models.ForeignKey(Trombi, verbose_name=_("trombi"), related_name='users', blank=True, null=True, on_delete=models.SET_NULL) - profile_pict = models.ImageField(upload_to='trombi', verbose_name=_("profile pict"), null=True, blank=True, - help_text=_("The profile picture you want in the trombi (warning: this picture may be published)")) - scrub_pict = models.ImageField(upload_to='trombi', verbose_name=_("scrub pict"), null=True, blank=True, - help_text=_("The scrub picture you want in the trombi (warning: this picture may be published)")) + + user = models.OneToOneField( + User, verbose_name=_("trombi user"), related_name="trombi_user" + ) + trombi = models.ForeignKey( + Trombi, + verbose_name=_("trombi"), + related_name="users", + blank=True, + null=True, + on_delete=models.SET_NULL, + ) + profile_pict = models.ImageField( + upload_to="trombi", + verbose_name=_("profile pict"), + null=True, + blank=True, + help_text=_( + "The profile picture you want in the trombi (warning: this picture may be published)" + ), + ) + scrub_pict = models.ImageField( + upload_to="trombi", + verbose_name=_("scrub pict"), + null=True, + blank=True, + help_text=_( + "The scrub picture you want in the trombi (warning: this picture may be published)" + ), + ) def __str__(self): return str(self.user) @@ -108,7 +154,9 @@ class TrombiUser(models.Model): def make_memberships(self): self.memberships.all().delete() - for m in self.user.memberships.filter(role__gt=settings.SITH_MAXIMUM_FREE_ROLE).order_by('end_date'): + for m in self.user.memberships.filter( + role__gt=settings.SITH_MAXIMUM_FREE_ROLE + ).order_by("end_date"): role = str(settings.SITH_CLUB_ROLES[m.role]) if m.description: role += " (%s)" % m.description @@ -130,8 +178,13 @@ class TrombiComment(models.Model): This represent a comment given by someone to someone else in the same Trombi instance. """ - author = models.ForeignKey(TrombiUser, verbose_name=_("author"), related_name='given_comments') - target = models.ForeignKey(TrombiUser, verbose_name=_("target"), related_name='received_comments') + + author = models.ForeignKey( + TrombiUser, verbose_name=_("author"), related_name="given_comments" + ) + target = models.ForeignKey( + TrombiUser, verbose_name=_("target"), related_name="received_comments" + ) content = models.TextField(_("content"), default="") is_moderated = models.BooleanField(_("is the comment moderated"), default=False) @@ -145,14 +198,17 @@ class TrombiClubMembership(models.Model): """ This represent a membership to a club """ - user = models.ForeignKey(TrombiUser, verbose_name=_("user"), related_name='memberships') + + user = models.ForeignKey( + TrombiUser, verbose_name=_("user"), related_name="memberships" + ) club = models.CharField(_("club"), max_length=32, default="") role = models.CharField(_("role"), max_length=64, default="") start = models.CharField(_("start"), max_length=16, default="") end = models.CharField(_("end"), max_length=16, default="") class Meta: - ordering = ['id'] + ordering = ["id"] def __str__(self): return "%s - %s - %s (%s)" % (self.user, self.club, self.role, self.start) @@ -161,4 +217,4 @@ class TrombiClubMembership(models.Model): return user.id == self.user.user.id or user.can_edit(self.user.trombi) def get_absolute_url(self): - return reverse('trombi:profile') + return reverse("trombi:profile") diff --git a/trombi/urls.py b/trombi/urls.py index a1a7085e..a20b2722 100644 --- a/trombi/urls.py +++ b/trombi/urls.py @@ -27,20 +27,56 @@ from django.conf.urls import url from trombi.views import * urlpatterns = [ - url(r'^(?P[0-9]+)/new$', TrombiCreateView.as_view(), name='create'), - url(r'^(?P[0-9]+)/export$', TrombiExportView.as_view(), name='export'), - url(r'^(?P[0-9]+)/edit$', TrombiEditView.as_view(), name='edit'), - url(r'^(?P[0-9]+)/moderate_comments$', TrombiModerateCommentsView.as_view(), name='moderate_comments'), - url(r'^(?P[0-9]+)/moderate$', TrombiModerateCommentView.as_view(), name='moderate_comment'), - url(r'^user/(?P[0-9]+)/delete$', TrombiDeleteUserView.as_view(), name='delete_user'), - url(r'^(?P[0-9]+)$', TrombiDetailView.as_view(), name='detail'), - url(r'^(?P[0-9]+)/new_comment$', TrombiCommentCreateView.as_view(), name='new_comment'), - url(r'^(?P[0-9]+)/profile$', UserTrombiProfileView.as_view(), name='user_profile'), - url(r'^comment/(?P[0-9]+)/edit$', TrombiCommentEditView.as_view(), name='edit_comment'), - url(r'^tools$', UserTrombiToolsView.as_view(), name='user_tools'), - url(r'^profile$', UserTrombiEditProfileView.as_view(), name='profile'), - url(r'^pictures$', UserTrombiEditPicturesView.as_view(), name='pictures'), - url(r'^reset_memberships$', UserTrombiResetClubMembershipsView.as_view(), name='reset_memberships'), - url(r'^membership/(?P[0-9]+)/edit$', UserTrombiEditMembershipView.as_view(), name='edit_membership'), - url(r'^membership/(?P[0-9]+)/delete$', UserTrombiDeleteMembershipView.as_view(), name='delete_membership'), + url(r"^(?P[0-9]+)/new$", TrombiCreateView.as_view(), name="create"), + url(r"^(?P[0-9]+)/export$", TrombiExportView.as_view(), name="export"), + url(r"^(?P[0-9]+)/edit$", TrombiEditView.as_view(), name="edit"), + url( + r"^(?P[0-9]+)/moderate_comments$", + TrombiModerateCommentsView.as_view(), + name="moderate_comments", + ), + url( + r"^(?P[0-9]+)/moderate$", + TrombiModerateCommentView.as_view(), + name="moderate_comment", + ), + url( + r"^user/(?P[0-9]+)/delete$", + TrombiDeleteUserView.as_view(), + name="delete_user", + ), + url(r"^(?P[0-9]+)$", TrombiDetailView.as_view(), name="detail"), + url( + r"^(?P[0-9]+)/new_comment$", + TrombiCommentCreateView.as_view(), + name="new_comment", + ), + url( + r"^(?P[0-9]+)/profile$", + UserTrombiProfileView.as_view(), + name="user_profile", + ), + url( + r"^comment/(?P[0-9]+)/edit$", + TrombiCommentEditView.as_view(), + name="edit_comment", + ), + url(r"^tools$", UserTrombiToolsView.as_view(), name="user_tools"), + url(r"^profile$", UserTrombiEditProfileView.as_view(), name="profile"), + url(r"^pictures$", UserTrombiEditPicturesView.as_view(), name="pictures"), + url( + r"^reset_memberships$", + UserTrombiResetClubMembershipsView.as_view(), + name="reset_memberships", + ), + url( + r"^membership/(?P[0-9]+)/edit$", + UserTrombiEditMembershipView.as_view(), + name="edit_membership", + ), + url( + r"^membership/(?P[0-9]+)/delete$", + UserTrombiDeleteMembershipView.as_view(), + name="delete_membership", + ), ] diff --git a/trombi/views.py b/trombi/views.py index 2b28bc59..86bc324e 100644 --- a/trombi/views.py +++ b/trombi/views.py @@ -38,7 +38,14 @@ from datetime import date from trombi.models import Trombi, TrombiUser, TrombiComment, TrombiClubMembership from core.views.forms import SelectDate -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, CanCreateMixin, QuickNotifMixin +from core.views import ( + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + TabedViewMixin, + CanCreateMixin, + QuickNotifMixin, +) from core.models import User from club.models import Club @@ -49,29 +56,35 @@ class TrombiTabsMixin(TabedViewMixin): def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse('trombi:user_tools'), - 'slug': 'tools', - 'name': _("Tools"), - }) - tab_list.append({ - 'url': reverse('trombi:profile'), - 'slug': 'profile', - 'name': _("My profile"), - }) - tab_list.append({ - 'url': reverse('trombi:pictures'), - 'slug': 'pictures', - 'name': _("My pictures"), - }) + tab_list.append( + {"url": reverse("trombi:user_tools"), "slug": "tools", "name": _("Tools")} + ) + tab_list.append( + { + "url": reverse("trombi:profile"), + "slug": "profile", + "name": _("My profile"), + } + ) + tab_list.append( + { + "url": reverse("trombi:pictures"), + "slug": "pictures", + "name": _("My pictures"), + } + ) try: trombi = self.request.user.trombi_user.trombi if self.request.user.is_owner(trombi): - tab_list.append({ - 'url': reverse('trombi:detail', kwargs={'trombi_id': trombi.id}), - 'slug': 'admin_tools', - 'name': _("Admin tools"), - }) + tab_list.append( + { + "url": reverse( + "trombi:detail", kwargs={"trombi_id": trombi.id} + ), + "slug": "admin_tools", + "name": _("Admin tools"), + } + ) except: pass return tab_list @@ -80,20 +93,23 @@ class TrombiTabsMixin(TabedViewMixin): class TrombiForm(forms.ModelForm): class Meta: model = Trombi - fields = ['subscription_deadline', 'comments_deadline', 'max_chars', 'show_profiles'] - widgets = { - 'subscription_deadline': SelectDate, - 'comments_deadline': SelectDate, - } + fields = [ + "subscription_deadline", + "comments_deadline", + "max_chars", + "show_profiles", + ] + widgets = {"subscription_deadline": SelectDate, "comments_deadline": SelectDate} class TrombiCreateView(CanCreateMixin, CreateView): """ Create a trombi for a club """ + model = Trombi form_class = TrombiForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def post(self, request, *args, **kwargs): """ @@ -101,7 +117,7 @@ class TrombiCreateView(CanCreateMixin, CreateView): """ form = self.get_form() if form.is_valid(): - club = get_object_or_404(Club, id=self.kwargs['club_id']) + club = get_object_or_404(Club, id=self.kwargs["club_id"]) form.instance.club = club ret = self.form_valid(form) return ret @@ -112,8 +128,8 @@ class TrombiCreateView(CanCreateMixin, CreateView): class TrombiEditView(CanEditPropMixin, TrombiTabsMixin, UpdateView): model = Trombi form_class = TrombiForm - template_name = 'core/edit.jinja' - pk_url_kwarg = 'trombi_id' + template_name = "core/edit.jinja" + pk_url_kwarg = "trombi_id" current_tab = "admin_tools" def get_success_url(self): @@ -121,12 +137,15 @@ class TrombiEditView(CanEditPropMixin, TrombiTabsMixin, UpdateView): class AddUserForm(forms.Form): - user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None) + user = AutoCompleteSelectField( + "users", required=True, label=_("Select user"), help_text=None + ) + class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailView): model = Trombi - template_name = 'trombi/detail.jinja' - pk_url_kwarg = 'trombi_id' + template_name = "trombi/detail.jinja" + pk_url_kwarg = "trombi_id" current_tab = "admin_tools" def post(self, request, *args, **kwargs): @@ -134,43 +153,51 @@ class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailVie form = AddUserForm(request.POST) if form.is_valid(): try: - TrombiUser(user=form.cleaned_data['user'], trombi=self.object).save() + TrombiUser(user=form.cleaned_data["user"], trombi=self.object).save() self.quick_notif_list.append("qn_success") - except: # We don't care about duplicate keys + except: # We don't care about duplicate keys self.quick_notif_list.append("qn_fail") return super(TrombiDetailView, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(TrombiDetailView, self).get_context_data(**kwargs) - kwargs['form'] = AddUserForm() + kwargs["form"] = AddUserForm() return kwargs + class TrombiExportView(CanEditMixin, TrombiTabsMixin, DetailView): model = Trombi - template_name = 'trombi/export.jinja' - pk_url_kwarg = 'trombi_id' + template_name = "trombi/export.jinja" + pk_url_kwarg = "trombi_id" current_tab = "admin_tools" + class TrombiDeleteUserView(CanEditPropMixin, TrombiTabsMixin, DeleteView): model = TrombiUser - pk_url_kwarg = 'user_id' - template_name = 'core/delete_confirm.jinja' + pk_url_kwarg = "user_id" + template_name = "core/delete_confirm.jinja" current_tab = "admin_tools" def get_success_url(self): - return reverse('trombi:detail', kwargs={'trombi_id': self.object.trombi.id}) + "?qn_success" + return ( + reverse("trombi:detail", kwargs={"trombi_id": self.object.trombi.id}) + + "?qn_success" + ) -class TrombiModerateCommentsView(CanEditPropMixin, QuickNotifMixin, TrombiTabsMixin, DetailView): +class TrombiModerateCommentsView( + CanEditPropMixin, QuickNotifMixin, TrombiTabsMixin, DetailView +): model = Trombi - template_name = 'trombi/comment_moderation.jinja' - pk_url_kwarg = 'trombi_id' + template_name = "trombi/comment_moderation.jinja" + pk_url_kwarg = "trombi_id" current_tab = "admin_tools" def get_context_data(self, **kwargs): kwargs = super(TrombiModerateCommentsView, self).get_context_data(**kwargs) - kwargs['comments'] = TrombiComment.objects.filter(is_moderated=False, - author__trombi__id=self.object.id).exclude(target__user__id=self.request.user.id) + kwargs["comments"] = TrombiComment.objects.filter( + is_moderated=False, author__trombi__id=self.object.id + ).exclude(target__user__id=self.request.user.id) return kwargs @@ -181,8 +208,8 @@ class TrombiModerateForm(forms.Form): class TrombiModerateCommentView(DetailView): model = TrombiComment - template_name = 'core/edit.jinja' - pk_url_kwarg = 'comment_id' + template_name = "core/edit.jinja" + pk_url_kwarg = "comment_id" def dispatch(self, request, *args, **kwargs): self.object = self.get_object() @@ -192,79 +219,107 @@ class TrombiModerateCommentView(DetailView): def post(self, request, *args, **kwargs): if "action" in request.POST: - if request.POST['action'] == "accept": + if request.POST["action"] == "accept": self.object.is_moderated = True self.object.save() - return redirect(reverse('trombi:moderate_comments', kwargs={'trombi_id': self.object.author.trombi.id}) + "?qn_success") - elif request.POST['action'] == "reject": - return super(TrombiModerateCommentView, self).get(request, *args, **kwargs) - elif request.POST['action'] == "delete" and "reason" in request.POST.keys(): + return redirect( + reverse( + "trombi:moderate_comments", + kwargs={"trombi_id": self.object.author.trombi.id}, + ) + + "?qn_success" + ) + elif request.POST["action"] == "reject": + return super(TrombiModerateCommentView, self).get( + request, *args, **kwargs + ) + elif request.POST["action"] == "delete" and "reason" in request.POST.keys(): self.object.author.user.email_user( subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")), - message=_("Your comment to %(target)s on the Trombi \"%(trombi)s\" was rejected for the following " - "reason: %(reason)s\n\n" - "Your comment was:\n\n%(content)s" - ) % { - 'target': self.object.target.user.get_display_name(), - 'trombi': self.object.author.trombi, - 'reason': request.POST["reason"], - 'content': self.object.content, + message=_( + 'Your comment to %(target)s on the Trombi "%(trombi)s" was rejected for the following ' + "reason: %(reason)s\n\n" + "Your comment was:\n\n%(content)s" + ) + % { + "target": self.object.target.user.get_display_name(), + "trombi": self.object.author.trombi, + "reason": request.POST["reason"], + "content": self.object.content, }, ) self.object.delete() - return redirect(reverse('trombi:moderate_comments', kwargs={'trombi_id': self.object.author.trombi.id}) + "?qn_success") + return redirect( + reverse( + "trombi:moderate_comments", + kwargs={"trombi_id": self.object.author.trombi.id}, + ) + + "?qn_success" + ) raise Http404 def get_context_data(self, **kwargs): kwargs = super(TrombiModerateCommentView, self).get_context_data(**kwargs) - kwargs['form'] = TrombiModerateForm() + kwargs["form"] = TrombiModerateForm() return kwargs + # User side class TrombiModelChoiceField(forms.ModelChoiceField): def label_from_instance(self, obj): - return _("%(name)s (deadline: %(date)s)") % {'name': str(obj), 'date': str(obj.subscription_deadline)} + return _("%(name)s (deadline: %(date)s)") % { + "name": str(obj), + "date": str(obj.subscription_deadline), + } class UserTrombiForm(forms.Form): - trombi = TrombiModelChoiceField(Trombi.availables.all(), required=False, label=_("Select trombi"), - help_text=_("This allows you to subscribe to a Trombi. " - "Be aware that you can subscribe only once, so don't play with that, " - "or you will expose yourself to the admins' wrath!")) + trombi = TrombiModelChoiceField( + Trombi.availables.all(), + required=False, + label=_("Select trombi"), + help_text=_( + "This allows you to subscribe to a Trombi. " + "Be aware that you can subscribe only once, so don't play with that, " + "or you will expose yourself to the admins' wrath!" + ), + ) class UserTrombiToolsView(QuickNotifMixin, TrombiTabsMixin, TemplateView): """ Display a user's trombi tools """ + template_name = "trombi/user_tools.jinja" current_tab = "tools" def post(self, request, *args, **kwargs): self.form = UserTrombiForm(request.POST) if self.form.is_valid(): - trombi_user = TrombiUser(user=request.user, - trombi=self.form.cleaned_data['trombi']) + trombi_user = TrombiUser( + user=request.user, trombi=self.form.cleaned_data["trombi"] + ) trombi_user.save() - self.quick_notif_list += ['qn_success'] + self.quick_notif_list += ["qn_success"] return super(UserTrombiToolsView, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(UserTrombiToolsView, self).get_context_data(**kwargs) - kwargs['user'] = self.request.user - if not hasattr(self.request.user, 'trombi_user'): - kwargs['subscribe_form'] = UserTrombiForm() + kwargs["user"] = self.request.user + if not hasattr(self.request.user, "trombi_user"): + kwargs["subscribe_form"] = UserTrombiForm() else: - kwargs['trombi'] = self.request.user.trombi_user.trombi - kwargs['date'] = date + kwargs["trombi"] = self.request.user.trombi_user.trombi + kwargs["date"] = date return kwargs class UserTrombiEditPicturesView(TrombiTabsMixin, UpdateView): model = TrombiUser - fields = ['profile_pict', 'scrub_pict'] + fields = ["profile_pict", "scrub_pict"] template_name = "core/edit.jinja" current_tab = "pictures" @@ -272,19 +327,27 @@ class UserTrombiEditPicturesView(TrombiTabsMixin, UpdateView): return self.request.user.trombi_user def get_success_url(self): - return reverse('trombi:user_tools') + "?qn_success" + return reverse("trombi:user_tools") + "?qn_success" class UserTrombiEditProfileView(QuickNotifMixin, TrombiTabsMixin, UpdateView): model = User - form_class = modelform_factory(User, - fields=['second_email', 'phone', 'department', 'dpt_option', - 'quote', 'parent_address'], - labels={ - 'second_email': _("Personal email (not UTBM)"), - 'phone': _("Phone"), - 'parent_address': _("Native town"), - }) + form_class = modelform_factory( + User, + fields=[ + "second_email", + "phone", + "department", + "dpt_option", + "quote", + "parent_address", + ], + labels={ + "second_email": _("Personal email (not UTBM)"), + "phone": _("Phone"), + "parent_address": _("Native town"), + }, + ) template_name = "trombi/edit_profile.jinja" current_tab = "profile" @@ -292,7 +355,7 @@ class UserTrombiEditProfileView(QuickNotifMixin, TrombiTabsMixin, UpdateView): return self.request.user def get_success_url(self): - return reverse('trombi:user_tools') + "?qn_success" + return reverse("trombi:user_tools") + "?qn_success" class UserTrombiResetClubMembershipsView(RedirectView): @@ -304,29 +367,34 @@ class UserTrombiResetClubMembershipsView(RedirectView): return redirect(self.get_success_url()) def get_success_url(self): - return reverse('trombi:profile') + "?qn_success" + return reverse("trombi:profile") + "?qn_success" class UserTrombiDeleteMembershipView(TrombiTabsMixin, CanEditMixin, DeleteView): model = TrombiClubMembership pk_url_kwarg = "membership_id" template_name = "core/delete_confirm.jinja" - success_url = reverse_lazy('trombi:profile') + success_url = reverse_lazy("trombi:profile") current_tab = "profile" def get_success_url(self): - return super(UserTrombiDeleteMembershipView, self).get_success_url() + "?qn_success" + return ( + super(UserTrombiDeleteMembershipView, self).get_success_url() + + "?qn_success" + ) class UserTrombiEditMembershipView(CanEditMixin, TrombiTabsMixin, UpdateView): model = TrombiClubMembership pk_url_kwarg = "membership_id" - fields = ['role', 'start', 'end'] + fields = ["role", "start", "end"] template_name = "core/edit.jinja" current_tab = "profile" def get_success_url(self): - return super(UserTrombiEditMembershipView, self).get_success_url() + "?qn_success" + return ( + super(UserTrombiEditMembershipView, self).get_success_url() + "?qn_success" + ) class UserTrombiProfileView(TrombiTabsMixin, DetailView): @@ -338,52 +406,69 @@ class UserTrombiProfileView(TrombiTabsMixin, DetailView): def get(self, request, *args, **kwargs): self.object = self.get_object() - if (self.object.trombi.id != request.user.trombi_user.trombi.id or - self.object.user.id == request.user.id or - not self.object.trombi.show_profiles): + if ( + self.object.trombi.id != request.user.trombi_user.trombi.id + or self.object.user.id == request.user.id + or not self.object.trombi.show_profiles + ): raise Http404() return super(UserTrombiProfileView, self).get(request, *args, **kwargs) -class TrombiCommentFormView(): +class TrombiCommentFormView: """ Create/edit a trombi comment """ + model = TrombiComment - fields = ['content'] - template_name = 'trombi/comment.jinja' + fields = ["content"] + template_name = "trombi/comment.jinja" def get_form_class(self): self.trombi = self.request.user.trombi_user.trombi if date.today() <= self.trombi.subscription_deadline: - raise Http404(_("You can not yet write comment, you must wait for " - "the subscription deadline to be passed.")) + raise Http404( + _( + "You can not yet write comment, you must wait for " + "the subscription deadline to be passed." + ) + ) if self.trombi.comments_deadline < date.today(): - raise Http404(_("You can not write comment anymore, the deadline is " - "already passed.")) - return modelform_factory(self.model, fields=self.fields, - widgets={ - 'content': forms.widgets.Textarea(attrs={'maxlength': self.trombi.max_chars}) - }, - help_texts={ - 'content': _("Maximum characters: %(max_length)s") % {'max_length': self.trombi.max_chars} - }) + raise Http404( + _( + "You can not write comment anymore, the deadline is " + "already passed." + ) + ) + return modelform_factory( + self.model, + fields=self.fields, + widgets={ + "content": forms.widgets.Textarea( + attrs={"maxlength": self.trombi.max_chars} + ) + }, + help_texts={ + "content": _("Maximum characters: %(max_length)s") + % {"max_length": self.trombi.max_chars} + }, + ) def get_success_url(self): - return reverse('trombi:user_tools') + "?qn_success" + return reverse("trombi:user_tools") + "?qn_success" def get_context_data(self, **kwargs): kwargs = super(TrombiCommentFormView, self).get_context_data(**kwargs) - if 'user_id' in self.kwargs.keys(): - kwargs['target'] = get_object_or_404(TrombiUser, id=self.kwargs['user_id']) + if "user_id" in self.kwargs.keys(): + kwargs["target"] = get_object_or_404(TrombiUser, id=self.kwargs["user_id"]) else: - kwargs['target'] = self.object.target + kwargs["target"] = self.object.target return kwargs class TrombiCommentCreateView(TrombiCommentFormView, CreateView): def form_valid(self, form): - target = get_object_or_404(TrombiUser, id=self.kwargs['user_id']) + target = get_object_or_404(TrombiUser, id=self.kwargs["user_id"]) author = self.request.user.trombi_user form.instance.author = author form.instance.target = target From 839f585f873b3bd7502aad1f79fa1e0b9f9ac751 Mon Sep 17 00:00:00 2001 From: klmp200 Date: Thu, 4 Oct 2018 21:35:39 +0200 Subject: [PATCH 4/7] CI: improve CI --- .gitlab-ci.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e94b7d00..57f7f4ec 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,19 +1,13 @@ stages: - - setup - test -setup: - stage: setup +test: + stage: test script: - apt-get update - apt-get install -y gettext - pip install -r requirements.txt - pip install coverage - - pip install black - -test: - stage: test - script: - ./manage.py compilemessages - coverage run ./manage.py test - coverage html @@ -25,4 +19,5 @@ test: black: stage: test script: + - pip install black - black --check . From d56a5138a8043b2f794ecfdc81bfc9377d4f687c Mon Sep 17 00:00:00 2001 From: klmp200 Date: Fri, 5 Oct 2018 22:30:54 +0200 Subject: [PATCH 5/7] contributing: freshen up infos while I'm at it --- CONTRIBUTING.md | 5 ++--- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9dc4aeab..4805805e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Hey ! Tu veux devenir un mec bien et en plus devenir bon en python si tu l'es pas déjà ? Il se trouve que le sith AE prévu pour l'été 2016 a besoin de toi ! -Pour faire le sith, on utilise le framework Web [Django](https://docs.djangoproject.com/fr/1.8/intro/) +Pour faire le sith, on utilise le framework Web [Django](https://docs.djangoproject.com/fr/1.11/intro/) N'hésite pas à lire les tutos et à nous demander (ae.info@utbm.fr). Bon, passons aux choses sérieuses, pour bidouiller le sith sans le casser : @@ -31,7 +31,6 @@ Maintenant, faut passer le sith en mode debug dans le fichier de settings person Enfin, il s'agit de créer la base de donnée de test lors de la première utilisation ./manage.py setup - répondre no Et pour lancer le sith, tu fais `python3 manage.py runserver` @@ -41,7 +40,7 @@ Va, et que l'AE soit avec toi. Sites et doc cools ------------------ -[Classy Class-Based Views](http://ccbv.co.uk/projects/Django/1.8/) +[Classy Class-Based Views](http://ccbv.co.uk/projects/Django/1.11/) Helpers: diff --git a/requirements.txt b/requirements.txt index 6c0b57a2..d06618de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -# Django 1.8 LTS is required, version 1.9 is not supported +# Django 1.11 LTS is required Django >=1.11, <2.0 Pillow mistune From 66957750bad8e97d02468c05436e298ce0f643f2 Mon Sep 17 00:00:00 2001 From: Soldat Date: Fri, 5 Oct 2018 23:03:45 +0200 Subject: [PATCH 6/7] Ajout de black dans contributing --- .gitignore | 1 + CONTRIBUTING.md | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/.gitignore b/.gitignore index c350ed9e..76fd8b83 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ db.sqlite3 *.mo *__pycache__* .DS_Store +.vscode/ env/ doc/html data/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4805805e..02b1a74f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,6 +37,24 @@ Et pour lancer le sith, tu fais `python3 manage.py runserver` Voilà, c'est le sith AE. Il y a des issues dans le gitlab qui sont à régler. Si tu as un domaine qui t'intéresse, une appli que tu voudrais développer, n'hésites pas et contacte-nous. Va, et que l'AE soit avec toi. +Black +------------------ +Pour uniformiser le formattage du code nous utilisons [Black](https://github.com/ambv/black). Cela permet d'avoir le même codestyle et donc le codereview prend moins de temps. Tout etant dans le même format, il est plus facile pour chacun de comprendre le code de chacun ! Cela permet aussi d'éviter des erreurs (y parait 🤷‍♀️). + +Installation de black: + + cd Sith + virtualenv --clear --python=python3 env_sith + source env_sith/bin/activate + pip install black + +Sous VsCode: +Ajouter ces deux lignes dans les settings de VsCode + + "python.formatting.provider": "black", + "editor.formatOnSave": true, + + Sites et doc cools ------------------ From d2fe9e56cfa49c1f7214b7b15f7aad2c8c499e84 Mon Sep 17 00:00:00 2001 From: klmp200 Date: Fri, 5 Oct 2018 23:18:35 +0200 Subject: [PATCH 7/7] Add black for sublime text in contributing --- CONTRIBUTING.md | 51 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02b1a74f..3d68371e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,43 +17,76 @@ Ensuite, tu fais : `git clone https://ae-dev.utbm.fr/ae/Sith.git` Avec cette commande, tu clones le sith AE dans le dossier courant. +```bash cd Sith virtualenv --clear --python=python3 env_sith source env_sith/bin/activate pip install -r requirements.txt +``` Maintenant, faut passer le sith en mode debug dans le fichier de settings personnalisé. +```bash echo "DEBUG=True" > sith/settings_custom.py echo 'EXTERNAL_RES = "False"' >> sith/settings_custom.py echo 'SITH_URL = "localhost:8000"' >> sith/settings_custom.py +``` Enfin, il s'agit de créer la base de donnée de test lors de la première utilisation +```bash ./manage.py setup +``` Et pour lancer le sith, tu fais `python3 manage.py runserver` Voilà, c'est le sith AE. Il y a des issues dans le gitlab qui sont à régler. Si tu as un domaine qui t'intéresse, une appli que tu voudrais développer, n'hésites pas et contacte-nous. Va, et que l'AE soit avec toi. -Black ------------------- -Pour uniformiser le formattage du code nous utilisons [Black](https://github.com/ambv/black). Cela permet d'avoir le même codestyle et donc le codereview prend moins de temps. Tout etant dans le même format, il est plus facile pour chacun de comprendre le code de chacun ! Cela permet aussi d'éviter des erreurs (y parait 🤷‍♀️). +# Black + +Pour uniformiser le formattage du code nous utilisons [Black](https://github.com/ambv/black). Cela permet d'avoir le même codestyle et donc le codereview prend moins de temps. Tout étant dans le même format, il est plus facile pour chacun de comprendre le code de chacun ! Cela permet aussi d'éviter des erreurs (y parait 🤷‍♀️). Installation de black: - cd Sith - virtualenv --clear --python=python3 env_sith - source env_sith/bin/activate +```bash pip install black - -Sous VsCode: +``` + +## Sous VsCode: +Attention, pour VsCode, Black doit être installé dans votre virtualenv ! Ajouter ces deux lignes dans les settings de VsCode +```json +{ "python.formatting.provider": "black", - "editor.formatOnSave": true, + "editor.formatOnSave": true +} +``` +## Sous Sublime Text +Il faut installer le plugin [sublack](https://packagecontrol.io/packages/sublack) depuis Package Control. + +Il suffit ensuite d'ajouter dans les settings du projet (ou en global) + +```json +{ + "sublack.black_on_save": true +} +``` + +Si vous utilisez le plugin [anaconda](http://damnwidget.github.io/anaconda/), pensez à modifier les paramètres du linter pep8 pour éviter de recevoir des warnings dans le formatage de black + +```json +{ + "pep8_ignore": [ + "E203", + "E266", + "E501", + "W503" + ] +} +``` Sites et doc cools ------------------