229 lines
8.3 KiB
Python
229 lines
8.3 KiB
Python
import textwrap
|
|
from functools import partial
|
|
|
|
from django import forms
|
|
from django.contrib.admin import helpers
|
|
from django.core import validators
|
|
from django.forms.models import modelformset_factory, BaseModelFormSet
|
|
from django.template import Template, Context
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from orchestra.forms.widgets import SpanWidget
|
|
|
|
from ..core.validators import validate_password
|
|
|
|
|
|
class AdminFormMixin(object):
|
|
""" Provides a method for rendering a form just like in Django Admin """
|
|
def as_admin(self):
|
|
prepopulated_fields = {}
|
|
fieldsets = [
|
|
(None, {
|
|
'fields': list(self.fields.keys())
|
|
}),
|
|
]
|
|
adminform = helpers.AdminForm(self, fieldsets, prepopulated_fields)
|
|
template = Template(
|
|
'{% for fieldset in adminform %}'
|
|
' {% include "admin/includes/fieldset.html" %}'
|
|
'{% endfor %}'
|
|
)
|
|
context = {
|
|
'adminform': adminform
|
|
}
|
|
return template.render(Context(context))
|
|
|
|
|
|
class AdminFormSet(BaseModelFormSet):
|
|
def as_admin(self):
|
|
template = Template(textwrap.dedent("""\
|
|
<div class="inline-group">
|
|
<div class="tabular inline-related last-related">
|
|
{{ formset.management_form }}
|
|
<fieldset class="module">
|
|
{{ formset.non_form_errors.as_ul }}
|
|
<table id="formset" class="form">
|
|
{% for form in formset.forms %}
|
|
{% if forloop.first %}
|
|
<thead><tr>
|
|
{% for field in form.visible_fields %}
|
|
<th>{{ field.label|capfirst }}</th>
|
|
{% endfor %}
|
|
</tr></thead>
|
|
{% endif %}
|
|
<tr class="{% cycle 'row1' 'row2' %}">
|
|
{% for field in form.visible_fields %}
|
|
<td>
|
|
{# Include the hidden fields in the form #}
|
|
{% if forloop.first %}
|
|
{% for hidden in form.hidden_fields %}
|
|
{{ hidden }}
|
|
{% endfor %}
|
|
{% endif %}
|
|
{{ field.errors.as_ul }}
|
|
{{ field }}
|
|
</td>
|
|
{% endfor %}
|
|
</tr>
|
|
{% endfor %}
|
|
</table>
|
|
</fieldset>
|
|
</div>
|
|
</div>""")
|
|
)
|
|
context = {
|
|
'formset': self
|
|
}
|
|
return template.render(Context(context))
|
|
|
|
|
|
class AdminPasswordChangeForm(forms.Form):
|
|
"""
|
|
A form used to change the password of a user in the admin interface.
|
|
"""
|
|
error_messages = {
|
|
'password_mismatch': _("The two password fields didn't match."),
|
|
'password_missing': _("No password has been provided."),
|
|
'bad_hash': _("Invalid password format or unknown hashing algorithm."),
|
|
}
|
|
required_css_class = 'required'
|
|
password = forms.CharField(label=_("Password"), required=False,
|
|
widget=forms.TextInput(attrs={'size':'120'}))
|
|
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput,
|
|
required=False, validators=[validate_password])
|
|
password2 = forms.CharField(label=_("Password (again)"), widget=forms.PasswordInput,
|
|
required=False)
|
|
|
|
def __init__(self, user, *args, **kwargs):
|
|
self.related = kwargs.pop('related', [])
|
|
self.raw = kwargs.pop('raw', False)
|
|
self.user = user
|
|
super().__init__(*args, **kwargs)
|
|
self.password_provided = False
|
|
for ix, rel in enumerate(self.related):
|
|
self.fields['password_%i' % ix] = forms.CharField(label=_("Password"), required=False,
|
|
widget=forms.TextInput(attrs={'size':'120'}))
|
|
setattr(self, 'clean_password_%i' % ix, partial(self.clean_password, ix=ix))
|
|
self.fields['password1_%i' % ix] = forms.CharField(label=_("Password"),
|
|
widget=forms.PasswordInput, required=False)
|
|
self.fields['password2_%i' % ix] = forms.CharField(label=_("Password (again)"),
|
|
widget=forms.PasswordInput, required=False)
|
|
setattr(self, 'clean_password2_%i' % ix, partial(self.clean_password2, ix=ix))
|
|
|
|
def clean_password2(self, ix=''):
|
|
if ix != '':
|
|
ix = '_%i' % ix
|
|
password1 = self.cleaned_data.get('password1%s' % ix)
|
|
password2 = self.cleaned_data.get('password2%s' % ix)
|
|
if password1 and password2:
|
|
self.password_provided = True
|
|
if password1 != password2:
|
|
raise forms.ValidationError(
|
|
self.error_messages['password_mismatch'],
|
|
code='password_mismatch',
|
|
)
|
|
elif password1 or password2:
|
|
self.password_provided = True
|
|
raise forms.ValidationError(
|
|
self.error_messages['password_mismatch'],
|
|
code='password_mismatch',
|
|
)
|
|
return password2
|
|
|
|
def clean_password(self, ix=''):
|
|
if ix != '':
|
|
ix = '_%i' % ix
|
|
password = self.cleaned_data.get('password%s' % ix)
|
|
if password:
|
|
# lazy loading because of passlib
|
|
from django.contrib.auth.hashers import identify_hasher
|
|
self.password_provided = True
|
|
try:
|
|
identify_hasher(password)
|
|
except ValueError:
|
|
raise forms.ValidationError(
|
|
self.error_messages['bad_hash'],
|
|
code='bad_hash',
|
|
)
|
|
return password
|
|
|
|
def clean(self):
|
|
if not self.password_provided:
|
|
raise forms.ValidationError(
|
|
self.error_messages['password_missing'],
|
|
code='password_missing',
|
|
)
|
|
|
|
def save(self, commit=True):
|
|
"""
|
|
Saves the new password.
|
|
"""
|
|
field_name = 'password' if self.raw else 'password1'
|
|
password = self.cleaned_data[field_name]
|
|
if password:
|
|
if self.raw:
|
|
self.user.password = password
|
|
else:
|
|
self.user.set_password(password)
|
|
if commit:
|
|
try:
|
|
self.user.save(update_fields=['password'])
|
|
except ValueError:
|
|
# password is not a field but an attribute
|
|
self.user.save() # Trigger the backend
|
|
for ix, rel in enumerate(self.related):
|
|
password = self.cleaned_data['%s_%s' % (field_name, ix)]
|
|
if password:
|
|
if self.raw:
|
|
rel.password = password
|
|
else:
|
|
set_password = getattr(rel, 'set_password')
|
|
set_password(password)
|
|
if commit:
|
|
rel.save(update_fields=['password'])
|
|
return self.user
|
|
|
|
def _get_changed_data(self):
|
|
data = super().changed_data
|
|
for name in self.fields.keys():
|
|
if name not in data:
|
|
return []
|
|
return ['password']
|
|
changed_data = property(_get_changed_data)
|
|
|
|
|
|
class SendEmailForm(forms.Form):
|
|
email_from = forms.EmailField(label=_("From"),
|
|
widget=forms.TextInput(attrs={'size': '118'}))
|
|
to = forms.CharField(label="To", required=False)
|
|
extra_to = forms.CharField(label="To (extra)", required=False,
|
|
widget=forms.TextInput(attrs={'size': '118'}))
|
|
subject = forms.CharField(label=_("Subject"),
|
|
widget=forms.TextInput(attrs={'size': '118'}))
|
|
message = forms.CharField(label=_("Message"),
|
|
widget=forms.Textarea(attrs={'cols': 118, 'rows': 15}))
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
initial = kwargs.get('initial')
|
|
if 'to' in initial:
|
|
self.fields['to'].widget = SpanWidget(original=initial['to'])
|
|
else:
|
|
self.fields.pop('to')
|
|
|
|
def clean_comma_separated_emails(self, value):
|
|
clean_value = []
|
|
for email in value.split(','):
|
|
email = email.strip()
|
|
if email:
|
|
try:
|
|
validators.validate_email(email)
|
|
except validators.ValidationError:
|
|
raise validators.ValidationError("Comma separated email addresses.")
|
|
clean_value.append(email)
|
|
return clean_value
|
|
|
|
def clean_extra_to(self):
|
|
extra_to = self.cleaned_data['extra_to']
|
|
return self.clean_comma_separated_emails(extra_to)
|