mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-02-06 07:13:00 +00:00
commit
7303fb9943
2
.gitignore
vendored
2
.gitignore
vendored
@ -37,7 +37,7 @@ coverage
|
|||||||
# Local data
|
# Local data
|
||||||
secrets.py
|
secrets.py
|
||||||
*.log
|
*.log
|
||||||
|
media/
|
||||||
# Virtualenv
|
# Virtualenv
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
|
@ -15,8 +15,10 @@ urlpatterns = [
|
|||||||
path('user/', views.UserListView.as_view(), name="user_list"),
|
path('user/', views.UserListView.as_view(), name="user_list"),
|
||||||
path('user/<int:pk>', views.UserDetailView.as_view(), name="user_detail"),
|
path('user/<int:pk>', views.UserDetailView.as_view(), name="user_detail"),
|
||||||
path('user/<int:pk>/update', views.UserUpdateView.as_view(), name="user_update_profile"),
|
path('user/<int:pk>/update', views.UserUpdateView.as_view(), name="user_update_profile"),
|
||||||
|
path('user/<int:pk>/update_pic', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"),
|
||||||
|
path('user/<int:pk>/aliases', views.AliasView.as_view(), name="user_alias"),
|
||||||
|
path('user/aliases/delete/<int:pk>', views.DeleteAliasView.as_view(), name="user_alias_delete"),
|
||||||
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
|
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
|
||||||
|
|
||||||
# API for the user autocompleter
|
# API for the user autocompleter
|
||||||
path('user/user-autocomplete', views.UserAutocomplete.as_view(), name="user_autocomplete"),
|
path('user/user-autocomplete', views.UserAutocomplete.as_view(), name="user_autocomplete"),
|
||||||
]
|
]
|
||||||
|
@ -1,19 +1,28 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from dal import autocomplete
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.translation import gettext_lazy as _
|
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.contrib.auth.models import User
|
||||||
|
from django.contrib import messages
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.conf import settings
|
||||||
from django_tables2.views import SingleTableView
|
from django_tables2.views import SingleTableView
|
||||||
from rest_framework.authtoken.models import Token
|
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 import Alias, NoteUser
|
||||||
from note.models.transactions import Transaction
|
from note.models.transactions import Transaction
|
||||||
from note.tables import HistoryTable
|
from note.tables import HistoryTable, AliasTable
|
||||||
|
from note.forms import AliasForm, ImageForm
|
||||||
|
|
||||||
from .models import Profile, Club, Membership
|
from .models import Profile, Club, Membership
|
||||||
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper
|
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper
|
||||||
@ -52,30 +61,25 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
fields = ['first_name', 'last_name', 'username', 'email']
|
fields = ['first_name', 'last_name', 'username', 'email']
|
||||||
template_name = 'member/profile_update.html'
|
template_name = 'member/profile_update.html'
|
||||||
context_object_name = 'user_object'
|
context_object_name = 'user_object'
|
||||||
second_form = ProfileForm
|
profile_form = ProfileForm
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["profile_form"] = self.second_form(
|
context['profile_form'] = self.profile_form(instance=context['user_object'].profile)
|
||||||
instance=context['user_object'].profile)
|
|
||||||
context['title'] = _("Update Profile")
|
context['title'] = _("Update Profile")
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
form = super().get_form(form_class)
|
form = super().get_form(form_class)
|
||||||
if 'username' not in form.data:
|
if 'username' not in form.data:
|
||||||
return form
|
return form
|
||||||
|
|
||||||
new_username = form.data['username']
|
new_username = form.data['username']
|
||||||
|
|
||||||
# Si l'utilisateur cherche à modifier son pseudo, le nouveau pseudo ne doit pas être proche d'un alias existant
|
# Si l'utilisateur cherche à modifier son pseudo, le nouveau pseudo ne doit pas être proche d'un alias existant
|
||||||
note = NoteUser.objects.filter(
|
note = NoteUser.objects.filter(
|
||||||
alias__normalized_name=Alias.normalize(new_username))
|
alias__normalized_name=Alias.normalize(new_username))
|
||||||
if note.exists() and note.get().user != self.object:
|
if note.exists() and note.get().user != self.object:
|
||||||
form.add_error('username',
|
form.add_error('username',
|
||||||
_("An alias with a similar name already exists."))
|
_("An alias with a similar name already exists."))
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@ -153,6 +157,103 @@ class UserListView(LoginRequiredMixin, SingleTableView):
|
|||||||
context["filter"] = self.filter
|
context["filter"] = self.filter
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
class AliasView(LoginRequiredMixin,FormMixin,DetailView):
|
||||||
|
model = User
|
||||||
|
template_name = 'member/profile_alias.html'
|
||||||
|
context_object_name = 'user_object'
|
||||||
|
form_class = AliasForm
|
||||||
|
|
||||||
|
def get_context_data(self,**kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
note = context['user_object'].note
|
||||||
|
context["aliases"] = AliasTable(note.alias_set.all())
|
||||||
|
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()
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
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(self.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)
|
||||||
|
|
||||||
|
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):
|
||||||
|
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']
|
||||||
|
# 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):
|
class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
||||||
"""
|
"""
|
||||||
|
@ -3,9 +3,37 @@
|
|||||||
|
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext_lazy as _
|
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
|
||||||
|
|
||||||
from .models import Transaction, TransactionTemplate, TemplateTransaction
|
from .models import Transaction, TransactionTemplate, TemplateTransaction
|
||||||
|
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.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 TransactionTemplateForm(forms.ModelForm):
|
||||||
|
@ -43,7 +43,10 @@ class Note(PolymorphicModel):
|
|||||||
display_image = models.ImageField(
|
display_image = models.ImageField(
|
||||||
verbose_name=_('display image'),
|
verbose_name=_('display image'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
blank=True,
|
blank=False,
|
||||||
|
null=False,
|
||||||
|
upload_to='pic/',
|
||||||
|
default='pic/default.png'
|
||||||
)
|
)
|
||||||
created_at = models.DateTimeField(
|
created_at = models.DateTimeField(
|
||||||
verbose_name=_('created at'),
|
verbose_name=_('created at'),
|
||||||
@ -219,14 +222,6 @@ class Alias(models.Model):
|
|||||||
if all(not unicodedata.category(char).startswith(cat)
|
if all(not unicodedata.category(char).startswith(cat)
|
||||||
for cat in {'M', 'P', 'Z', 'C'})).casefold()
|
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):
|
def clean(self):
|
||||||
normalized_name = Alias.normalize(self.name)
|
normalized_name = Alias.normalize(self.name)
|
||||||
if len(normalized_name) >= 255:
|
if len(normalized_name) >= 255:
|
||||||
@ -235,11 +230,12 @@ class Alias(models.Model):
|
|||||||
try:
|
try:
|
||||||
sim_alias = Alias.objects.get(normalized_name=normalized_name)
|
sim_alias = Alias.objects.get(normalized_name=normalized_name)
|
||||||
if self != sim_alias:
|
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"
|
code="same_alias"
|
||||||
)
|
)
|
||||||
except Alias.DoesNotExist:
|
except Alias.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
self.normalized_name = normalized_name
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
if self.name == str(self.note):
|
if self.name == str(self.note):
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
|
from django_tables2.utils import A
|
||||||
from .models.transactions import Transaction
|
from .models.transactions import Transaction
|
||||||
|
from .models.notes import Alias
|
||||||
|
|
||||||
class HistoryTable(tables.Table):
|
class HistoryTable(tables.Table):
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -24,3 +24,22 @@ class HistoryTable(tables.Table):
|
|||||||
queryset = queryset.annotate(total=F('amount') * F('quantity')) \
|
queryset = queryset.annotate(total=F('amount') * F('quantity')) \
|
||||||
.order_by(('-' if is_descending else '') + 'total')
|
.order_by(('-' if is_descending else '') + 'total')
|
||||||
return (queryset, True)
|
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'
|
||||||
|
|
||||||
|
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')
|
||||||
|
BIN
media/pic/default.png
Normal file
BIN
media/pic/default.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
@ -96,6 +96,7 @@ TEMPLATES = [
|
|||||||
'django.contrib.auth.context_processors.auth',
|
'django.contrib.auth.context_processors.auth',
|
||||||
'django.contrib.messages.context_processors.messages',
|
'django.contrib.messages.context_processors.messages',
|
||||||
'django.template.context_processors.request',
|
'django.template.context_processors.request',
|
||||||
|
# 'django.template.context_processors.media',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -193,6 +194,13 @@ STATIC_URL = '/static/'
|
|||||||
|
|
||||||
ALIAS_VALIDATOR_REGEX = r''
|
ALIAS_VALIDATOR_REGEX = r''
|
||||||
|
|
||||||
|
MEDIA_ROOT=os.path.join(BASE_DIR,"media")
|
||||||
|
MEDIA_URL='/media/'
|
||||||
|
|
||||||
|
# Profile Picture Settings
|
||||||
|
PIC_WIDTH = 200
|
||||||
|
PIC_RATIO = 1
|
||||||
|
|
||||||
# CAS Settings
|
# CAS Settings
|
||||||
CAS_AUTO_CREATE_USER = False
|
CAS_AUTO_CREATE_USER = False
|
||||||
CAS_LOGO_URL = "/static/img/Saperlistpopette.png"
|
CAS_LOGO_URL = "/static/img/Saperlistpopette.png"
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from cas import views as cas_views
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from cas import views as cas_views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Dev so redirect to something random
|
# Dev so redirect to something random
|
||||||
@ -30,3 +33,6 @@ urlpatterns = [
|
|||||||
# Include Django REST API
|
# Include Django REST API
|
||||||
path('api/', include('api.urls')),
|
path('api/', include('api.urls')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
|
||||||
|
urlpatterns += static(settings.STATIC_URL,document_root=settings.STATIC_ROOT)
|
||||||
|
19
templates/member/profile_alias.html
Normal file
19
templates/member/profile_alias.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "member/profile_detail.html" %}
|
||||||
|
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block profile_content %}
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<form class=" text-center form my-2" action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form |crispy }}
|
||||||
|
<button class="btn btn-primary mx-2" type="submit">
|
||||||
|
{% trans "Add alias" %}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="card bg-light shadow">
|
||||||
|
<div class="card-body">
|
||||||
|
{% render_table aliases %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -5,7 +5,11 @@
|
|||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-md-3 mb-4">
|
<div class="col-md-3 mb-4">
|
||||||
<div class="card bg-light shadow">
|
<div class="card bg-light shadow">
|
||||||
<img src="{{ object.note.display_image }}" class="card-img-top" alt="">
|
<div class="card-top text-center">
|
||||||
|
<a href="{% url 'member:user_update_pic' object.pk %}">
|
||||||
|
<img src="{{ object.note.display_image.url }}" class="img-thumbnail mt-2" >
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-xl-6">{% trans 'name'|capfirst %}, {% trans 'first name' %}</dt>
|
<dt class="col-xl-6">{% trans 'name'|capfirst %}, {% trans 'first name' %}</dt>
|
||||||
@ -30,21 +34,25 @@
|
|||||||
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ object.note.balance | pretty_money }}</dd>
|
<dd class="col-xl-6">{{ object.note.balance | pretty_money }}</dd>
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
<dt class="col-xl-6"> <a href="{% url 'member:user_alias' object.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
|
||||||
<dd class="col-xl-6">{{ object.note.alias_set.all|join:", " }}</dd>
|
<dd class="col-xl-6 text-truncate">{{ object.note.alias_set.all|join:", " }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
{% if object.pk == user.pk %}
|
{% if object.pk == user.pk %}
|
||||||
<a class="small" href="{% url 'member:auth_token' %}">{% trans 'Manage auth token' %}</a>
|
<a class="small" href="{% url 'member:auth_token' %}">{% trans 'Manage auth token' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer text-center">
|
||||||
<a class="btn btn-primary btn-sm" href="{% url 'member:user_update_profile' object.pk %}">{% trans 'Update Profile' %}</a>
|
<a class="btn btn-primary btn-sm" href="{% url 'member:user_update_profile' object.pk %}">{% trans 'Update Profile' %}</a>
|
||||||
|
{% url 'member:user_detail' object.pk as user_profile_url %}
|
||||||
|
{%if request.get_full_path != user_profile_url %}
|
||||||
|
<a class="btn btn-primary btn-sm" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
|
{% block profile_content %}
|
||||||
<div class="accordion shadow" id="accordionProfile">
|
<div class="accordion shadow" id="accordionProfile">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
@ -72,6 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
97
templates/member/profile_picture_update.html
Normal file
97
templates/member/profile_picture_update.html
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
{% extends "member/profile_detail.html" %}
|
||||||
|
{% load i18n static pretty_money django_tables2 crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block profile_content %}
|
||||||
|
<div class="text-center">
|
||||||
|
<form method="post" enctype="multipart/form-data" id="formUpload">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form |crispy }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<!-- MODAL TO CROP THE IMAGE -->
|
||||||
|
<div class="modal fade" id="modalCrop">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-body">
|
||||||
|
<img src="" id="modal-image" style="max-width: 100%;">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="btn-group pull-left" role="group">
|
||||||
|
<button type="button" class="btn btn-default" id="js-zoom-in">
|
||||||
|
<span class="glyphicon glyphicon-zoom-in"></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default js-zoom-out">
|
||||||
|
<span class="glyphicon glyphicon-zoom-out"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Nevermind</button>
|
||||||
|
<button type="button" class="btn btn-primary js-crop-and-upload">Crop and upload</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block extracss %}
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.css" rel="stylesheet">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript%}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.6/cropper.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery-cropper@1.0.1/dist/jquery-cropper.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
/* SCRIPT TO OPEN THE MODAL WITH THE PREVIEW */
|
||||||
|
$("#id_image").change(function (e) {
|
||||||
|
if (this.files && this.files[0]) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function (e) {
|
||||||
|
$("#modal-image").attr("src", e.target.result);
|
||||||
|
$("#modalCrop").modal("show");
|
||||||
|
}
|
||||||
|
reader.readAsDataURL(this.files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user