From f77351b4443923c25e57cf7ce636f56fb6116822 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Fri, 28 Feb 2020 13:37:31 +0100 Subject: [PATCH 01/18] Add view for aliases --- apps/member/urls.py | 1 + apps/member/views.py | 35 ++++++++++++++- apps/note/forms.py | 6 +++ apps/note/tables.py | 15 ++++++- templates/member/profile_detail.html | 2 +- templates/member/user_alias.html | 65 ++++++++++++++++++++++++++++ 6 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 templates/member/user_alias.html diff --git a/apps/member/urls.py b/apps/member/urls.py index 6a7ed5ce..fdb2e911 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -15,6 +15,7 @@ urlpatterns = [ path('user/', views.UserListView.as_view(), name="user_list"), path('user/', views.UserDetailView.as_view(), name="user_detail"), path('user//update', views.UserUpdateView.as_view(), name="user_update_profile"), + path('user//aliases', views.AliasView.as_view(), name="user_alias"), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), # API for the user autocompleter diff --git a/apps/member/views.py b/apps/member/views.py index d03a94e0..3ff10e8a 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -6,14 +6,17 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import redirect from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, DetailView, UpdateView, TemplateView +from django.views.generic.edit import FormMixin from django.contrib.auth.models import User from django.urls import reverse_lazy from django.db.models import Q from django_tables2.views import SingleTableView from rest_framework.authtoken.models import Token + from note.models import Alias, NoteUser from note.models.transactions import Transaction -from note.tables import HistoryTable +from note.tables import HistoryTable, AliasTable +from note.forms import AliasForm from .models import Profile, Club, Membership from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper @@ -153,6 +156,36 @@ class UserListView(LoginRequiredMixin, SingleTableView): context["filter"] = self.filter return context +class AliasView(LoginRequiredMixin,FormMixin,DetailView): + model = User + template_name = 'member/user_alias.html' + context_object_name = 'user_object' + form_class = AliasForm + + def get_context_data(self,**kwargs): + context = super().get_context_data(**kwargs) + context["aliases"] = AliasTable(context['user_object'].note.alias_set.all()) + context["alias_form"] = AliasForm() + return context + + def get_success_url(self): + return reverse_lazy('member:user_alias', kwargs={'pk': self.object.id}) + + def post(self,request,*args,**kwargs): + self.object = self.get_object() + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + + def form_valid(self, form): + alias = form.save(commit=False) + alias.note = self.object.note + alias.save() + alias.note.save() + print(alias,alias.pk) + return super().form_valid(form) class ManageAuthTokens(LoginRequiredMixin, TemplateView): """ diff --git a/apps/note/forms.py b/apps/note/forms.py index 3222acec..d920b9aa 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -6,6 +6,12 @@ from django import forms from django.utils.translation import gettext_lazy as _ from .models import Transaction, TransactionTemplate, TemplateTransaction +from .models import Alias + +class AliasForm(forms.ModelForm): + class Meta: + model = Alias + fields = ("name",) class TransactionTemplateForm(forms.ModelForm): diff --git a/apps/note/tables.py b/apps/note/tables.py index 43a1ef74..2248c412 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -5,7 +5,7 @@ import django_tables2 as tables from django.db.models import F from .models.transactions import Transaction - +from .models.notes import Alias class HistoryTable(tables.Table): class Meta: @@ -24,3 +24,16 @@ class HistoryTable(tables.Table): queryset = queryset.annotate(total=F('amount') * F('quantity')) \ .order_by(('-' if is_descending else '') + 'total') return (queryset, True) + +class AliasTable(tables.Table): + class Meta: + attrs = { + 'class': + 'table table condensed table-striped table-hover' + } + model = Alias + fields = ('name',) + template_name = 'django_tables2/bootstrap4.html' + + delete = tables.LinkColumn('member:user_alias_delete', args=[A('id')], attrs={ + 'a': {'class': 'btn'} }) diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index 6b5c127a..0a19eecd 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -30,7 +30,7 @@
{% trans 'balance'|capfirst %}
{{ object.note.balance | pretty_money }}
-
{% trans 'aliases'|capfirst %}
+
{% trans 'aliases'|capfirst %}
{{ object.note.alias_set.all|join:", " }}
diff --git a/templates/member/user_alias.html b/templates/member/user_alias.html new file mode 100644 index 00000000..f1a06e7e --- /dev/null +++ b/templates/member/user_alias.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} +{% load i18n static pretty_money django_tables2 crispy_forms_tags %} + +{% block content %} +
+
+
+ +
+
+
{% trans 'name'|capfirst %}, {% trans 'first name' %}
+
{{ object.last_name }} {{ object.first_name }}
+ +
{% trans 'username'|capfirst %}
+
{{ object.username }}
+ +
{% trans 'password'|capfirst %}
+
+ + {% trans 'Change password' %} + +
+ +
{% trans 'section'|capfirst %}
+
{{ object.profile.section }}
+ +
{% trans 'address'|capfirst %}
+
{{ object.profile.address }}
+ +
{% trans 'balance'|capfirst %}
+
{{ object.note.balance | pretty_money }}
+ +
{% trans 'aliases'|capfirst %}
+
{{ object.note.alias_set.all|join:", " }}
+
+ + {% if object.pk == user.pk %} + {% trans 'Manage auth token' %} + {% endif %} +
+ +
+
+ +
+
+
+ {% csrf_token %} + {{ alias_form |crispy }} + +
+
+
+
+ {% render_table aliases %} +
+
+
+
+{% endblock %} From 9133c84ef2c9e42ac83d6c16c5ffc04292c83c3e Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Fri, 28 Feb 2020 13:49:50 +0100 Subject: [PATCH 02/18] comment bug --- apps/note/tables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/note/tables.py b/apps/note/tables.py index 2248c412..461b6aa2 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -35,5 +35,5 @@ class AliasTable(tables.Table): fields = ('name',) template_name = 'django_tables2/bootstrap4.html' - delete = tables.LinkColumn('member:user_alias_delete', args=[A('id')], attrs={ - 'a': {'class': 'btn'} }) + # delete = tables.LinkColumn('member:user_alias_delete', args=[A('id')], attrs={ + # 'a': {'class': 'btn'} }) From c77bc0c032663d9ec7a7a978e118af0fe80f9798 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Fri, 28 Feb 2020 13:52:15 +0100 Subject: [PATCH 03/18] add deleteView for aliases --- apps/member/urls.py | 2 +- apps/member/views.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/member/urls.py b/apps/member/urls.py index fdb2e911..652d1e1f 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -16,8 +16,8 @@ urlpatterns = [ path('user/', views.UserDetailView.as_view(), name="user_detail"), path('user//update', views.UserUpdateView.as_view(), name="user_update_profile"), path('user//aliases', views.AliasView.as_view(), name="user_alias"), + path('user/aliases/delete/', views.DeleteAliasView.as_view(), name="user_alias_delete"), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), - # API for the user autocompleter path('user/user-autocomplete', views.UserAutocomplete.as_view(), name="user_autocomplete"), ] diff --git a/apps/member/views.py b/apps/member/views.py index 3ff10e8a..4518c955 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -5,7 +5,7 @@ from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import redirect from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, DetailView, UpdateView, TemplateView +from django.views.generic import CreateView, DetailView, UpdateView, TemplateView,DeleteView from django.views.generic.edit import FormMixin from django.contrib.auth.models import User from django.urls import reverse_lazy @@ -187,6 +187,10 @@ class AliasView(LoginRequiredMixin,FormMixin,DetailView): print(alias,alias.pk) return super().form_valid(form) +class DeleteAliasView(DeleteView): + model = Alias + success_url = reverse_lazy('member:user_alias') + class ManageAuthTokens(LoginRequiredMixin, TemplateView): """ Affiche le jeton d'authentification, et permet de le regénérer From db9594457486421db63144eb011d795eb6ab0171 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Fri, 28 Feb 2020 15:25:45 +0100 Subject: [PATCH 04/18] add delete boutons, work with only one alias --- apps/member/views.py | 11 +++++++---- apps/note/tables.py | 8 ++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index 4518c955..14ba0556 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -187,10 +187,13 @@ class AliasView(LoginRequiredMixin,FormMixin,DetailView): print(alias,alias.pk) return super().form_valid(form) -class DeleteAliasView(DeleteView): - model = Alias - success_url = reverse_lazy('member:user_alias') - +class DeleteAliasView(LoginRequiredMixin, DeleteView): + model = Alias + def get_success_url(self): + return reverse_lazy('member:user_alias',kwargs={'pk':self.object.note.user.pk}) + def get(self, request, *args, **kwargs): + return self.post(request, *args, **kwargs) + class ManageAuthTokens(LoginRequiredMixin, TemplateView): """ Affiche le jeton d'authentification, et permet de le regénérer diff --git a/apps/note/tables.py b/apps/note/tables.py index 461b6aa2..c8dbf74a 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -3,7 +3,7 @@ import django_tables2 as tables from django.db.models import F - +from django_tables2.utils import A from .models.transactions import Transaction from .models.notes import Alias @@ -32,8 +32,8 @@ class AliasTable(tables.Table): 'table table condensed table-striped table-hover' } model = Alias - fields = ('name',) + fields = ('name','pk') template_name = 'django_tables2/bootstrap4.html' - # delete = tables.LinkColumn('member:user_alias_delete', args=[A('id')], attrs={ - # 'a': {'class': 'btn'} }) + delete = tables.LinkColumn('member:user_alias_delete', args=[A('pk')], attrs={ + 'a': {'class': 'btn btn-danger'} },text='delete') From 27391049f27d6656b112c264c011d06f5571eeb2 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Fri, 28 Feb 2020 16:12:35 +0100 Subject: [PATCH 05/18] remove duplicated form --- apps/member/views.py | 4 ++-- templates/member/user_alias.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index 14ba0556..ce3beb39 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -164,8 +164,8 @@ class AliasView(LoginRequiredMixin,FormMixin,DetailView): def get_context_data(self,**kwargs): context = super().get_context_data(**kwargs) - context["aliases"] = AliasTable(context['user_object'].note.alias_set.all()) - context["alias_form"] = AliasForm() + note = context['user_object'].note + context["aliases"] = AliasTable(note.alias_set.all()) return context def get_success_url(self): diff --git a/templates/member/user_alias.html b/templates/member/user_alias.html index f1a06e7e..4c6834d4 100644 --- a/templates/member/user_alias.html +++ b/templates/member/user_alias.html @@ -47,9 +47,9 @@
-
+ {% csrf_token %} - {{ alias_form |crispy }} + {{ form |crispy }} From 35d5bcdf0f495201b329dea5b1d5fa517bed9e3b Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Sun, 1 Mar 2020 00:25:53 +0100 Subject: [PATCH 06/18] Deletion of alias now possible! --- apps/member/views.py | 19 ++++++++++++++++++- apps/note/models/notes.py | 8 -------- apps/note/tables.py | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index ce3beb39..32b414a5 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -8,11 +8,14 @@ from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, DetailView, UpdateView, TemplateView,DeleteView from django.views.generic.edit import FormMixin from django.contrib.auth.models import User +from django.contrib import messages from django.urls import reverse_lazy +from django.http import HttpResponseRedirect + from django.db.models import Q from django_tables2.views import SingleTableView from rest_framework.authtoken.models import Token - +from django.core.exceptions import ValidationError from note.models import Alias, NoteUser from note.models.transactions import Transaction from note.tables import HistoryTable, AliasTable @@ -189,8 +192,22 @@ class AliasView(LoginRequiredMixin,FormMixin,DetailView): class DeleteAliasView(LoginRequiredMixin, DeleteView): model = Alias + + def delete(self,request,*args,**kwargs): + try: + self.object = self.get_object() + self.object.delete() + except ValidationError as e: + # TODO: pass message to redirected view. + messages.error(self.request,str(e)) + else: + messages.success(self.request,_("Alias successfully deleted")) + return HttpResponseRedirect(get_success_url) + def get_success_url(self): + print(self.request) return reverse_lazy('member:user_alias',kwargs={'pk':self.object.note.user.pk}) + def get(self, request, *args, **kwargs): return self.post(request, *args, **kwargs) diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py index 62811735..8ee83888 100644 --- a/apps/note/models/notes.py +++ b/apps/note/models/notes.py @@ -219,14 +219,6 @@ class Alias(models.Model): if all(not unicodedata.category(char).startswith(cat) for cat in {'M', 'P', 'Z', 'C'})).casefold() - def save(self, *args, **kwargs): - """ - Handle normalized_name - """ - self.normalized_name = Alias.normalize(self.name) - if len(self.normalized_name) < 256: - super().save(*args, **kwargs) - def clean(self): normalized_name = Alias.normalize(self.name) if len(normalized_name) >= 255: diff --git a/apps/note/tables.py b/apps/note/tables.py index c8dbf74a..588c3745 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -36,4 +36,4 @@ class AliasTable(tables.Table): template_name = 'django_tables2/bootstrap4.html' delete = tables.LinkColumn('member:user_alias_delete', args=[A('pk')], attrs={ - 'a': {'class': 'btn btn-danger'} },text='delete') + 'a': {'class': 'btn btn-danger'} },text='delete',accessor='pk') From b7e68722aaeaf8f6dabd69a1d8b6451ced421181 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Sun, 1 Mar 2020 13:42:22 +0100 Subject: [PATCH 07/18] front improvement on alias page --- apps/member/views.py | 3 +-- apps/note/forms.py | 8 ++++++-- apps/note/models/notes.py | 5 +++-- apps/note/tables.py | 12 +++++++++--- templates/member/user_alias.html | 4 ++-- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index 32b414a5..c214395d 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -186,7 +186,6 @@ class AliasView(LoginRequiredMixin,FormMixin,DetailView): alias = form.save(commit=False) alias.note = self.object.note alias.save() - alias.note.save() print(alias,alias.pk) return super().form_valid(form) @@ -202,7 +201,7 @@ class DeleteAliasView(LoginRequiredMixin, DeleteView): messages.error(self.request,str(e)) else: messages.success(self.request,_("Alias successfully deleted")) - return HttpResponseRedirect(get_success_url) + return HttpResponseRedirect(self.get_success_url()) def get_success_url(self): print(self.request) diff --git a/apps/note/forms.py b/apps/note/forms.py index d920b9aa..2998887b 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -12,8 +12,12 @@ class AliasForm(forms.ModelForm): class Meta: model = Alias fields = ("name",) - - + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + self.fields["name"].label = False + self.fields["name"].widget.attrs={"placeholder":_('New Alias')} + + class TransactionTemplateForm(forms.ModelForm): class Meta: model = TransactionTemplate diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py index 8ee83888..9e3c0a9e 100644 --- a/apps/note/models/notes.py +++ b/apps/note/models/notes.py @@ -227,12 +227,13 @@ class Alias(models.Model): try: sim_alias = Alias.objects.get(normalized_name=normalized_name) if self != sim_alias: - raise ValidationError(_('An alias with a similar name already exists:'), + raise ValidationError(_('An alias with a similar name already exists: {} '.format(sim_alias)), code="same_alias" ) except Alias.DoesNotExist: pass - + self.normalized_name = normalized_name + def delete(self, using=None, keep_parents=False): if self.name == str(self.note): raise ValidationError(_("You can't delete your main alias."), diff --git a/apps/note/tables.py b/apps/note/tables.py index 588c3745..20476cb6 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -32,8 +32,14 @@ class AliasTable(tables.Table): 'table table condensed table-striped table-hover' } model = Alias - fields = ('name','pk') + fields =('name',) template_name = 'django_tables2/bootstrap4.html' - delete = tables.LinkColumn('member:user_alias_delete', args=[A('pk')], attrs={ - 'a': {'class': 'btn btn-danger'} },text='delete',accessor='pk') + show_header = False + name = tables.Column(attrs={'td':{'class':'text-center'}}) + delete = tables.LinkColumn('member:user_alias_delete', + args=[A('pk')], + attrs={ + 'td': {'class':'col-sm-2'}, + 'a': {'class': 'btn btn-danger'} }, + text='delete',accessor='pk') diff --git a/templates/member/user_alias.html b/templates/member/user_alias.html index 4c6834d4..7d932adc 100644 --- a/templates/member/user_alias.html +++ b/templates/member/user_alias.html @@ -47,10 +47,10 @@
- + {% csrf_token %} {{ form |crispy }} - From b3d45ba64b353006e7cc1081599271199304a109 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Sun, 1 Mar 2020 17:36:49 +0100 Subject: [PATCH 08/18] alias page inherit from profile template --- templates/member/profile_detail.html | 11 ++++-- templates/member/user_alias.html | 50 ++-------------------------- 2 files changed, 10 insertions(+), 51 deletions(-) diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index 0a19eecd..f9f9eb03 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -31,20 +31,24 @@
{{ object.note.balance | pretty_money }}
{% trans 'aliases'|capfirst %}
-
{{ object.note.alias_set.all|join:", " }}
+
{{ object.note.alias_set.all|join:", " }}
{% if object.pk == user.pk %} {% trans 'Manage auth token' %} {% endif %}
-
-
+ {% block profile_content %}
@@ -72,6 +76,7 @@
+ {% endblock %}
{% endblock %} diff --git a/templates/member/user_alias.html b/templates/member/user_alias.html index 7d932adc..a83d7c3e 100644 --- a/templates/member/user_alias.html +++ b/templates/member/user_alias.html @@ -1,51 +1,7 @@ -{% extends "base.html" %} +{% extends "member/profile_detail.html" %} {% load i18n static pretty_money django_tables2 crispy_forms_tags %} -{% block content %} -
-
-
- -
-
-
{% trans 'name'|capfirst %}, {% trans 'first name' %}
-
{{ object.last_name }} {{ object.first_name }}
- -
{% trans 'username'|capfirst %}
-
{{ object.username }}
- -
{% trans 'password'|capfirst %}
-
- - {% trans 'Change password' %} - -
- -
{% trans 'section'|capfirst %}
-
{{ object.profile.section }}
- -
{% trans 'address'|capfirst %}
-
{{ object.profile.address }}
- -
{% trans 'balance'|capfirst %}
-
{{ object.note.balance | pretty_money }}
- -
{% trans 'aliases'|capfirst %}
-
{{ object.note.alias_set.all|join:", " }}
-
- - {% if object.pk == user.pk %} - {% trans 'Manage auth token' %} - {% endif %} -
- -
-
- -
+{% block profile_content %}
{% csrf_token %} @@ -60,6 +16,4 @@ {% render_table aliases %}
-
-
{% endblock %} From 901454f3383a80ab55ad558efe77d3ffb1a9d7b5 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Tue, 3 Mar 2020 11:05:02 +0100 Subject: [PATCH 09/18] rename template for consistency --- apps/member/views.py | 2 +- templates/member/{user_alias.html => profile_alias.html} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename templates/member/{user_alias.html => profile_alias.html} (100%) diff --git a/apps/member/views.py b/apps/member/views.py index c214395d..1ca902db 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -161,7 +161,7 @@ class UserListView(LoginRequiredMixin, SingleTableView): class AliasView(LoginRequiredMixin,FormMixin,DetailView): model = User - template_name = 'member/user_alias.html' + template_name = 'member/profile_alias.html' context_object_name = 'user_object' form_class = AliasForm diff --git a/templates/member/user_alias.html b/templates/member/profile_alias.html similarity index 100% rename from templates/member/user_alias.html rename to templates/member/profile_alias.html From 211859e9408f12ace085b5d738489726cd6867de Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Tue, 3 Mar 2020 14:24:07 +0100 Subject: [PATCH 10/18] add default profile pic --- apps/note/models/notes.py | 1 + media/pic/default.png | Bin 0 -> 9259 bytes note_kfet/settings/base.py | 3 +++ note_kfet/urls.py | 4 ++++ templates/member/profile_detail.html | 4 +++- 5 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 media/pic/default.png diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py index 9e3c0a9e..ea62bd75 100644 --- a/apps/note/models/notes.py +++ b/apps/note/models/notes.py @@ -44,6 +44,7 @@ class Note(PolymorphicModel): verbose_name=_('display image'), max_length=255, blank=True, + default='pic/default.png' ) created_at = models.DateTimeField( verbose_name=_('created at'), diff --git a/media/pic/default.png b/media/pic/default.png new file mode 100644 index 0000000000000000000000000000000000000000..f933bc341619178775520150d514effb2339b10a GIT binary patch literal 9259 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
- +
+ +
{% trans 'name'|capfirst %}, {% trans 'first name' %}
From f992c117f4c58ad8181cbc69ca8ac7b6aeac2317 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Tue, 3 Mar 2020 14:25:16 +0100 Subject: [PATCH 11/18] rename second_form to profile_form --- apps/member/views.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index 1ca902db..d2acb8f1 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -58,14 +58,12 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): fields = ['first_name', 'last_name', 'username', 'email'] template_name = 'member/profile_update.html' context_object_name = 'user_object' - second_form = ProfileForm + profile_form = ProfileForm def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["profile_form"] = self.second_form( - instance=context['user_object'].profile) + context['profile_form'] = self.profile_form(instance=context['user_object'].profile) context['title'] = _("Update Profile") - return context def get_form(self, form_class=None): From 14ccc8da7558a355896e91270153f5d40ebaa2d5 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Tue, 3 Mar 2020 14:26:31 +0100 Subject: [PATCH 12/18] Add ImageForm --- apps/note/forms.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/apps/note/forms.py b/apps/note/forms.py index 2998887b..27050207 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -5,19 +5,34 @@ from dal import autocomplete from django import forms from django.utils.translation import gettext_lazy as _ +from crispy_forms.helper import FormHelper +from crispy_forms.bootstrap import Div +from crispy_forms.layout import Layout, HTML + from .models import Transaction, TransactionTemplate, TemplateTransaction -from .models import Alias +from .models import Note, Alias class AliasForm(forms.ModelForm): class Meta: model = Alias fields = ("name",) + def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs) self.fields["name"].label = False self.fields["name"].widget.attrs={"placeholder":_('New Alias')} - + +class ImageForm(forms.ModelForm): + class Meta: + model = Note + fields = ('display_image',) + + def __init__(self,*args,**kwargs): + super().__init__(*args,**kwargs) + self.fields["display_image"].label = _("select an image") + self.fields["display_image"].widget.attrs={"help_text":_('Maximal size: 2MB')} + class TransactionTemplateForm(forms.ModelForm): class Meta: model = TransactionTemplate From c4198a64afa3b15e11b9169a26a3afc24c463d3a Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Tue, 3 Mar 2020 14:45:50 +0100 Subject: [PATCH 13/18] add button for profile pic view --- apps/member/urls.py | 1 + templates/member/profile_detail.html | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/member/urls.py b/apps/member/urls.py index 652d1e1f..87ae662b 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -15,6 +15,7 @@ urlpatterns = [ path('user/', views.UserListView.as_view(), name="user_list"), path('user/', views.UserDetailView.as_view(), name="user_detail"), path('user//update', views.UserUpdateView.as_view(), name="user_update_profile"), + path('user//update_pic', views.UserUpdateView.as_view(), name="user_update_pic"), path('user//aliases', views.AliasView.as_view(), name="user_alias"), path('user/aliases/delete/', views.DeleteAliasView.as_view(), name="user_alias_delete"), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index c3301fd6..930352bd 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -46,6 +46,7 @@ {%if request.get_full_path != user_profile_url %} {% trans 'View Profile' %} {% endif %} + {% trans 'Change Avatar' %}
From fd74080ce34453c730826910fb98e01394bbf6b3 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Wed, 4 Mar 2020 16:34:12 +0100 Subject: [PATCH 14/18] profile picture update view is working --- apps/member/urls.py | 2 +- apps/member/views.py | 31 +++++++++- apps/note/forms.py | 12 ++-- apps/note/models/notes.py | 3 +- templates/member/profile_detail.html | 5 +- templates/member/profile_picture_update.html | 63 ++++++++++++++++++++ 6 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 templates/member/profile_picture_update.html diff --git a/apps/member/urls.py b/apps/member/urls.py index 87ae662b..d9dfd181 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -15,7 +15,7 @@ urlpatterns = [ path('user/', views.UserListView.as_view(), name="user_list"), path('user/', views.UserDetailView.as_view(), name="user_detail"), path('user//update', views.UserUpdateView.as_view(), name="user_update_profile"), - path('user//update_pic', views.UserUpdateView.as_view(), name="user_update_pic"), + path('user//update_pic', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"), path('user//aliases', views.AliasView.as_view(), name="user_alias"), path('user/aliases/delete/', views.DeleteAliasView.as_view(), name="user_alias_delete"), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), diff --git a/apps/member/views.py b/apps/member/views.py index d2acb8f1..65f60cca 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -207,7 +207,36 @@ class DeleteAliasView(LoginRequiredMixin, DeleteView): def get(self, request, *args, **kwargs): return self.post(request, *args, **kwargs) - + +class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView): + model = User + template_name = 'member/profile_picture_update.html' + context_object_name = 'user_object' + form_class = ImageForm + def get_context_data(self,*args,**kwargs): + context = super().get_context_data(*args,**kwargs) + context['form'] = self.form_class(self.request.POST,self.request.FILES) + return context + + def get_success_url(self): + return reverse_lazy('member:user_detail', kwargs={'pk': self.object.id}) + + def post(self,request,*args,**kwargs): + form = self.get_form() + self.object = self.get_object() + if form.is_valid(): + return self.form_valid(form) + else: + print('is_invalid') + print(form) + return self.form_invalid(form) + + def form_valid(self,form): + print(form) + self.object.note.display_image = form.cleaned_data.get("image","pic/default.png") + self.object.note.save() + return super().form_valid(form) + class ManageAuthTokens(LoginRequiredMixin, TemplateView): """ Affiche le jeton d'authentification, et permet de le regénérer diff --git a/apps/note/forms.py b/apps/note/forms.py index 27050207..af6c97bc 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -23,15 +23,11 @@ class AliasForm(forms.ModelForm): self.fields["name"].widget.attrs={"placeholder":_('New Alias')} -class ImageForm(forms.ModelForm): - class Meta: - model = Note - fields = ('display_image',) +class ImageForm(forms.Form): + image = forms.ImageField(required = False, + label=_('select an image'), + help_text=_('Maximal size: 2MB')) - def __init__(self,*args,**kwargs): - super().__init__(*args,**kwargs) - self.fields["display_image"].label = _("select an image") - self.fields["display_image"].widget.attrs={"help_text":_('Maximal size: 2MB')} class TransactionTemplateForm(forms.ModelForm): class Meta: diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py index ea62bd75..81e851d8 100644 --- a/apps/note/models/notes.py +++ b/apps/note/models/notes.py @@ -43,7 +43,8 @@ class Note(PolymorphicModel): display_image = models.ImageField( verbose_name=_('display image'), max_length=255, - blank=True, + blank=False, + null=False, default='pic/default.png' ) created_at = models.DateTimeField( diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index 930352bd..e997b333 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -6,7 +6,9 @@
- + + +
@@ -46,7 +48,6 @@ {%if request.get_full_path != user_profile_url %} {% trans 'View Profile' %} {% endif %} - {% trans 'Change Avatar' %}
diff --git a/templates/member/profile_picture_update.html b/templates/member/profile_picture_update.html new file mode 100644 index 00000000..a5de13dc --- /dev/null +++ b/templates/member/profile_picture_update.html @@ -0,0 +1,63 @@ +{% extends "member/profile_detail.html" %} +{% load i18n static pretty_money django_tables2 crispy_forms_tags %} + +{% block profile_content %} +
+
+ +

{% trans "current image" %}

+
+
+ + {% csrf_token %} + {{ form |crispy }} + + +
+
+
+
+
+{% endblock %} +{% block extrajavascript%} + +{% endblock %} From 2e8505e9b4560ec88ea1ea50ec26a46b95cd89a0 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Wed, 4 Mar 2020 16:34:52 +0100 Subject: [PATCH 15/18] =?UTF-8?q?m=C3=A9nage=20dans=20les=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/member/views.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index 65f60cca..25dcbf8a 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -1,7 +1,6 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import redirect from django.utils.translation import gettext_lazy as _ @@ -11,15 +10,17 @@ from django.contrib.auth.models import User from django.contrib import messages from django.urls import reverse_lazy from django.http import HttpResponseRedirect - from django.db.models import Q +from django.core.exceptions import ValidationError + from django_tables2.views import SingleTableView from rest_framework.authtoken.models import Token -from django.core.exceptions import ValidationError +from dal import autocomplete + from note.models import Alias, NoteUser from note.models.transactions import Transaction from note.tables import HistoryTable, AliasTable -from note.forms import AliasForm +from note.forms import AliasForm, ImageForm from .models import Profile, Club, Membership from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper @@ -70,16 +71,13 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): form = super().get_form(form_class) if 'username' not in form.data: return form - new_username = form.data['username'] - # Si l'utilisateur cherche à modifier son pseudo, le nouveau pseudo ne doit pas être proche d'un alias existant note = NoteUser.objects.filter( alias__normalized_name=Alias.normalize(new_username)) if note.exists() and note.get().user != self.object: form.add_error('username', _("An alias with a similar name already exists.")) - return form def form_valid(self, form): @@ -184,7 +182,6 @@ class AliasView(LoginRequiredMixin,FormMixin,DetailView): alias = form.save(commit=False) alias.note = self.object.note alias.save() - print(alias,alias.pk) return super().form_valid(form) class DeleteAliasView(LoginRequiredMixin, DeleteView): From 08b97e722b8c9de31d273103bfe200068d96ddd3 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Thu, 5 Mar 2020 23:32:01 +0100 Subject: [PATCH 16/18] add support for cropping image. WIP. --- apps/member/views.py | 13 +- apps/note/forms.py | 9 +- note_kfet/settings/base.py | 4 + note_kfet/urls.py | 1 + templates/member/profile_picture_update.html | 130 ++++++++++++------- 5 files changed, 105 insertions(+), 52 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index 25dcbf8a..9b185cca 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -16,6 +16,7 @@ from django.core.exceptions import ValidationError from django_tables2.views import SingleTableView from rest_framework.authtoken.models import Token from dal import autocomplete +from PIL import Image from note.models import Alias, NoteUser from note.models.transactions import Transaction @@ -229,8 +230,16 @@ class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView): return self.form_invalid(form) def form_valid(self,form): - print(form) - self.object.note.display_image = form.cleaned_data.get("image","pic/default.png") + image_file_field = form.cleaned_data['image'] + x = form.cleaned_data['x'] + y = form.cleaned_data['y'] + w = form.cleaned_data['width'] + h = form.cleaned_data['height'] + with Image.open(image_file_field.name) as image: + image = image.crop((x, y, w+x, h+y)) + image.thumbnail((256, 256), Image.ANTIALIAS) + image.save(image_file_field.name,format=image.format) + self.object.note.display_image = image_file_field self.object.note.save() return super().form_valid(form) diff --git a/apps/note/forms.py b/apps/note/forms.py index af6c97bc..819ed97a 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -3,8 +3,11 @@ from dal import autocomplete from django import forms +from django.conf import settings from django.utils.translation import gettext_lazy as _ +import os + from crispy_forms.helper import FormHelper from crispy_forms.bootstrap import Div from crispy_forms.layout import Layout, HTML @@ -27,8 +30,12 @@ class ImageForm(forms.Form): image = forms.ImageField(required = False, label=_('select an image'), help_text=_('Maximal size: 2MB')) + x = forms.FloatField(widget=forms.HiddenInput()) + y = forms.FloatField(widget=forms.HiddenInput()) + width = forms.FloatField(widget=forms.HiddenInput()) + height = forms.FloatField(widget=forms.HiddenInput()) - + class TransactionTemplateForm(forms.ModelForm): class Meta: model = TransactionTemplate diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index fad5e6f7..63a07c03 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -191,3 +191,7 @@ ALIAS_VALIDATOR_REGEX = r'' MEDIA_ROOT=os.path.join(BASE_DIR,"media") MEDIA_URL='/media/' + +# Profile Picture Settings +PIC_WIDTH = 200 +PIC_RATIO = 1 diff --git a/note_kfet/urls.py b/note_kfet/urls.py index 385ce991..0a79e414 100644 --- a/note_kfet/urls.py +++ b/note_kfet/urls.py @@ -26,3 +26,4 @@ urlpatterns = [ ] urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT) +urlpatterns += static(settings.STATIC_URL,document_root=settings.STATIC_ROOT) diff --git a/templates/member/profile_picture_update.html b/templates/member/profile_picture_update.html index a5de13dc..f0e30834 100644 --- a/templates/member/profile_picture_update.html +++ b/templates/member/profile_picture_update.html @@ -2,62 +2,94 @@ {% load i18n static pretty_money django_tables2 crispy_forms_tags %} {% block profile_content %} -
-
- -

{% trans "current image" %}

-
-
-
- {% csrf_token %} - {{ form |crispy }} - -
-
-
-
-
+
+ {% csrf_token %} + {{ form |crispy }} +
+ + {% endblock %} +{% block extracss %} + +{% endblock %} + {% block extrajavascript%} - + + + /* SCRIPTS TO HANDLE THE CROPPER BOX */ + var $image = $("#modal-image"); + var cropBoxData; + var canvasData; + $("#modalCrop").on("shown.bs.modal", function () { + $image.cropper({ + viewMode: 1, + aspectRatio: 1/1, + minCropBoxWidth: 200, + minCropBoxHeight: 200, + ready: function () { + $image.cropper("setCanvasData", canvasData); + $image.cropper("setCropBoxData", cropBoxData); + } + }); + }).on("hidden.bs.modal", function () { + cropBoxData = $image.cropper("getCropBoxData"); + canvasData = $image.cropper("getCanvasData"); + $image.cropper("destroy"); + }); + + $(".js-zoom-in").click(function () { + $image.cropper("zoom", 0.1); + }); + + $(".js-zoom-out").click(function () { + $image.cropper("zoom", -0.1); + }); + + /* SCRIPT TO COLLECT THE DATA AND POST TO THE SERVER */ + $(".js-crop-and-upload").click(function () { + var cropData = $image.cropper("getData"); + $("#id_x").val(cropData["x"]); + $("#id_y").val(cropData["y"]); + $("#id_height").val(cropData["height"]); + $("#id_width").val(cropData["width"]); + $("#formUpload").submit(); + }); + + }); + {% endblock %} From 38feb693f37be7e86fef7cb9683098eb78ec5e93 Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Fri, 6 Mar 2020 20:07:15 +0100 Subject: [PATCH 17/18] crop,resized, and save picture at the right place --- .gitignore | 2 +- apps/member/views.py | 26 +++++++++++++++++++------- apps/note/models/notes.py | 1 + 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index b57ed74a..f9082403 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,7 @@ coverage # Local data secrets.py *.log - +media/ # Virtualenv env/ venv/ diff --git a/apps/member/views.py b/apps/member/views.py index 9b185cca..870079cc 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -12,11 +12,12 @@ from django.urls import reverse_lazy from django.http import HttpResponseRedirect from django.db.models import Q from django.core.exceptions import ValidationError - +from django.conf import settings from django_tables2.views import SingleTableView from rest_framework.authtoken.models import Token from dal import autocomplete from PIL import Image +import io from note.models import Alias, NoteUser from note.models.transactions import Transaction @@ -230,19 +231,30 @@ class ProfilePictureUpdateView(LoginRequiredMixin, FormMixin, DetailView): return self.form_invalid(form) def form_valid(self,form): - image_file_field = form.cleaned_data['image'] + image_field = form.cleaned_data['image'] x = form.cleaned_data['x'] y = form.cleaned_data['y'] w = form.cleaned_data['width'] h = form.cleaned_data['height'] - with Image.open(image_file_field.name) as image: - image = image.crop((x, y, w+x, h+y)) - image.thumbnail((256, 256), Image.ANTIALIAS) - image.save(image_file_field.name,format=image.format) - self.object.note.display_image = image_file_field + # image crop and resize + image_file = io.BytesIO(image_field.read()) + ext = image_field.name.split('.')[-1] + image = Image.open(image_file) + image = image.crop((x, y, x+w, y+h)) + image_clean = image.resize((settings.PIC_WIDTH, + settings.PIC_RATIO*settings.PIC_WIDTH), + Image.ANTIALIAS) + image_file = io.BytesIO() + image_clean.save(image_file,ext) + image_field.file = image_file + # renaming + filename = "{}_pic.{}".format(self.object.note.pk, ext) + image_field.name = filename + self.object.note.display_image = image_field self.object.note.save() return super().form_valid(form) + class ManageAuthTokens(LoginRequiredMixin, TemplateView): """ Affiche le jeton d'authentification, et permet de le regénérer diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py index 81e851d8..4b06c93a 100644 --- a/apps/note/models/notes.py +++ b/apps/note/models/notes.py @@ -45,6 +45,7 @@ class Note(PolymorphicModel): max_length=255, blank=False, null=False, + upload_to='pic/', default='pic/default.png' ) created_at = models.DateTimeField( From deb19e45c1eba91efafca37dfa87fb8762a03d8f Mon Sep 17 00:00:00 2001 From: Pierre-antoine Comby Date: Fri, 6 Mar 2020 20:16:44 +0100 Subject: [PATCH 18/18] center form --- templates/member/profile_picture_update.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/member/profile_picture_update.html b/templates/member/profile_picture_update.html index f0e30834..36e53dcd 100644 --- a/templates/member/profile_picture_update.html +++ b/templates/member/profile_picture_update.html @@ -2,10 +2,12 @@ {% load i18n static pretty_money django_tables2 crispy_forms_tags %} {% block profile_content %} +
{% csrf_token %} {{ form |crispy }}
+