0001-WIP-support-avatar-picture-in-user-profile-26022.patch
debian-jessie/control | ||
---|---|---|
28 | 28 |
python-jwcrypto (>= 0.3.1), |
29 | 29 |
python-cryptography (>= 1.3.4), |
30 | 30 |
python-django-filters (>= 1), |
31 |
python-django-filters (<< 2) |
|
31 |
python-django-filters (<< 2), |
|
32 |
python-sorl-thumbnail |
|
32 | 33 |
Provides: ${python:Provides} |
33 | 34 |
Recommends: python-ldap |
34 | 35 |
Suggests: python-raven |
debian-wheezy/control | ||
---|---|---|
25 | 25 |
python-markdown (>= 2.1), |
26 | 26 |
python-ldap (>= 2.4), |
27 | 27 |
python-six (>= 1.0), |
28 |
python-django-filters (>= 1) |
|
28 |
python-django-filters (>= 1), |
|
29 |
python-sorl-thumbnail |
|
29 | 30 |
Provides: ${python:Provides} |
30 | 31 |
Recommends: python-ldap |
31 | 32 |
Suggests: python-raven |
setup.py | ||
---|---|---|
131 | 131 |
'XStatic-jQuery', |
132 | 132 |
'XStatic-jquery-ui<1.12', |
133 | 133 |
'xstatic-select2', |
134 |
'sorl-thumbnail', |
|
134 | 135 |
], |
135 | 136 |
zip_safe=False, |
136 | 137 |
classifiers=[ |
src/authentic2/api_views.py | ||
---|---|---|
298 | 298 |
@cache_control(private=True, max_age=60) |
299 | 299 |
@decorators.json |
300 | 300 |
def user(request): |
301 |
if request.user.is_anonymous(): |
|
301 |
u = request.user |
|
302 |
if u.is_anonymous(): |
|
302 | 303 |
return {} |
303 |
return request.user.to_json()
|
|
304 |
return u.to_json(request)
|
|
304 | 305 | |
305 | 306 | |
306 | 307 |
def attributes_hash(attributes): |
src/authentic2/attribute_kinds.py | ||
---|---|---|
17 | 17 |
from .plugins import collect_from_plugins |
18 | 18 |
from . import app_settings |
19 | 19 |
from .forms import widgets |
20 |
from .utils import image_serialize |
|
20 | 21 | |
21 | 22 |
capfirst = allow_lazy(capfirst, unicode) |
22 | 23 | |
... | ... | |
91 | 92 |
default_validators = [validate_fr_postcode] |
92 | 93 | |
93 | 94 | |
95 |
class A2ImageDRFField(serializers.ImageField): |
|
96 |
def to_representation(self, value, *args, **kwargs): |
|
97 |
return self.context['request'].build_absolute_uri(value) |
|
98 | ||
99 | ||
94 | 100 |
DEFAULT_ALLOW_BLANK = True |
95 | 101 |
DEFAULT_MAX_LENGTH = 256 |
96 | 102 | |
... | ... | |
151 | 157 |
'field_class': PhoneNumberField, |
152 | 158 |
'rest_framework_field_class': PhoneNumberDRFField, |
153 | 159 |
}, |
160 |
{ |
|
161 |
'label': _('image'), |
|
162 |
'name': 'image', |
|
163 |
'field_class': forms.ImageField, |
|
164 |
'kwargs': { |
|
165 |
'widget': widgets.AttributeImageInput, |
|
166 |
}, |
|
167 |
'serialize': image_serialize, |
|
168 |
'serialize_eval_kwargs' : { |
|
169 |
'owner_uuid': 'owner.uuid', |
|
170 |
'owner_pk': 'owner.pk', |
|
171 |
'attr_label': 'self.label', |
|
172 |
}, |
|
173 |
'deserialize': lambda x: x, |
|
174 |
'rest_framework_field_class': A2ImageDRFField, |
|
175 |
'rest_framework_field_kwargs': { |
|
176 |
'read_only': True, |
|
177 |
}, |
|
178 |
'value_is_relative_uri': True, |
|
179 |
'thumbnail': { |
|
180 |
'crop': 'center', |
|
181 |
'height': '100', |
|
182 |
'width': '100', |
|
183 |
}, |
|
184 |
}, |
|
154 | 185 |
] |
155 | 186 | |
156 | 187 |
src/authentic2/custom_user/apps.py | ||
---|---|---|
10 | 10 |
from django.db.models.signals import post_migrate |
11 | 11 | |
12 | 12 |
post_migrate.connect( |
13 |
self.create_first_name_last_name_attributes,
|
|
13 |
self.create_custom_attributes,
|
|
14 | 14 |
sender=self) |
15 | 15 | |
16 |
def create_first_name_last_name_attributes(self, app_config, verbosity=2, interactive=True,
|
|
16 |
def create_custom_attributes(self, app_config, verbosity=2, interactive=True,
|
|
17 | 17 |
using=DEFAULT_DB_ALIAS, **kwargs): |
18 | 18 |
from django.utils import translation |
19 | 19 |
from django.utils.translation import ugettext_lazy as _ |
... | ... | |
34 | 34 |
content_type = ContentType.objects.get_for_model(User) |
35 | 35 | |
36 | 36 |
attrs = {} |
37 |
attrs['avatar_picture'], created = Attribute.objects.get_or_create( |
|
38 |
name='avatar_picture', |
|
39 |
defaults={'kind': 'image', |
|
40 |
'label': _('Avatar picture'), |
|
41 |
'required': False, |
|
42 |
'asked_on_registration': False, |
|
43 |
'user_editable': True, |
|
44 |
'user_visible': True}) |
|
37 | 45 |
attrs['first_name'], created = Attribute.objects.get_or_create( |
38 | 46 |
name='first_name', |
39 | 47 |
defaults={'kind': 'string', |
... | ... | |
51 | 59 |
'user_editable': True, |
52 | 60 |
'user_visible': True}) |
53 | 61 | |
54 |
serialize = get_kind('string').get('serialize') |
|
55 | 62 |
for user in User.objects.all(): |
56 |
for attr_name in attrs: |
|
63 |
for at in attrs: |
|
64 |
serialize = get_kind(at.kind).get('serialize') |
|
57 | 65 |
av, created = AttributeValue.objects.get_or_create( |
58 | 66 |
content_type=content_type, |
59 | 67 |
object_id=user.id, |
60 |
attribute=attrs[attr_name],
|
|
68 |
attribute=attrs[at], |
|
61 | 69 |
defaults={ |
62 | 70 |
'multiple': False, |
63 | 71 |
'verified': False, |
64 |
'content': serialize(getattr(user, attr_name, None))
|
|
72 |
'content': serialize(getattr(user, at, None)) |
|
65 | 73 |
}) |
src/authentic2/custom_user/models.py | ||
---|---|---|
219 | 219 |
def has_verified_attributes(self): |
220 | 220 |
return AttributeValue.objects.with_owner(self).filter(verified=True).exists() |
221 | 221 | |
222 |
def to_json(self): |
|
222 |
def to_json(self, request):
|
|
223 | 223 |
d = {} |
224 | 224 |
for av in AttributeValue.objects.with_owner(self): |
225 |
d[str(av.attribute.name)] = av.to_python() |
|
225 |
d[str(av.attribute.name)] = av.to_python(request)
|
|
226 | 226 |
d.update({ |
227 | 227 |
'uuid': self.uuid, |
228 | 228 |
'username': self.username, |
src/authentic2/forms/widgets.py | ||
---|---|---|
11 | 11 |
import re |
12 | 12 |
import uuid |
13 | 13 | |
14 |
from django.forms.widgets import DateTimeInput, DateInput, TimeInput |
|
14 |
from django.forms.widgets import DateTimeInput, DateInput, TimeInput, \ |
|
15 |
ClearableFileInput |
|
15 | 16 |
from django.forms.widgets import PasswordInput as BasePasswordInput |
16 | 17 |
from django.utils.formats import get_language, get_format |
17 | 18 |
from django.utils.safestring import mark_safe |
... | ... | |
246 | 247 |
json.dumps(_id), |
247 | 248 |
) |
248 | 249 |
return output |
250 | ||
251 | ||
252 |
class AttributeImageInput(ClearableFileInput): |
|
253 |
# template_name = "authentic2/accounts_image.html" # Django 1.11 only todo |
|
254 |
template_with_initial = ( |
|
255 |
'%(initial_text)s: <br /> <img src="%(initial)s"/> <br />' |
|
256 |
'%(clear_template)s<br />%(input_text)s: %(input)s' |
|
257 |
) |
|
258 | ||
259 |
def is_initial(self, value): |
|
260 |
return bool(value) |
|
261 | ||
262 |
def get_template_substitution_values(self, value): |
|
263 |
subs_values = dict() |
|
264 |
subs_values.update({ |
|
265 |
'initial': value, |
|
266 |
}) |
|
267 |
return subs_values |
src/authentic2/models.py | ||
---|---|---|
1 |
import logging |
|
1 | 2 |
import time |
2 | 3 |
import urlparse |
3 | 4 |
import uuid |
... | ... | |
203 | 204 |
return kind['default'] |
204 | 205 | |
205 | 206 |
def set_value(self, owner, value, verified=False): |
207 |
logger = logging.getLogger(__name__) |
|
206 | 208 |
serialize = self.get_kind()['serialize'] |
207 | 209 |
# setting to None is to delete |
208 | 210 |
if value is None: |
... | ... | |
225 | 227 |
av.verified = verified |
226 | 228 |
av.save() |
227 | 229 |
else: |
228 |
content = serialize(value) |
|
230 |
kwargs = dict() |
|
231 |
for key, flat_value in self.get_kind().get('serialize_eval_kwargs', {}).items(): |
|
232 |
try: |
|
233 |
evalue = eval(flat_value) |
|
234 |
except NameError: |
|
235 |
logger.error("Couldn't evaluate {} for attribute <{}: {}>".format( |
|
236 |
flat_value, |
|
237 |
self.get_kind()['kind'], |
|
238 |
self.label)) |
|
239 |
continue |
|
240 |
kwargs.update({key: evalue}) |
|
241 |
if kwargs: |
|
242 |
content = serialize(value, **kwargs) |
|
243 |
else: |
|
244 |
content = serialize(value) |
|
245 | ||
229 | 246 |
av, created = AttributeValue.objects.get_or_create( |
230 | 247 |
content_type=ContentType.objects.get_for_model(owner), |
231 | 248 |
object_id=owner.pk, |
... | ... | |
275 | 292 | |
276 | 293 |
objects = managers.AttributeValueManager() |
277 | 294 | |
278 |
def to_python(self): |
|
279 |
deserialize = self.attribute.get_kind()['deserialize'] |
|
280 |
return deserialize(self.content) |
|
295 |
def to_python(self, request=None): |
|
296 |
kind = self.attribute.get_kind() |
|
297 |
deserialize = kind['deserialize'] |
|
298 |
content = self.content |
|
299 |
if request and kind.get('value_is_relative_uri'): |
|
300 |
content = request.build_absolute_uri(content) |
|
301 |
return deserialize(content) |
|
281 | 302 | |
282 | 303 |
def natural_key(self): |
283 | 304 |
if not hasattr(self.owner, 'natural_key'): |
src/authentic2/settings.py | ||
---|---|---|
22 | 22 |
DEBUG = False |
23 | 23 |
DEBUG_DB = False |
24 | 24 |
MEDIA = 'media' |
25 |
MEDIA_ROOT = 'media' |
|
26 |
MEDIA_URL = '/media/' |
|
25 | 27 | |
26 | 28 |
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts |
27 | 29 |
ALLOWED_HOSTS = [] |
... | ... | |
132 | 134 |
'xstatic.pkg.jquery', |
133 | 135 |
'xstatic.pkg.jquery_ui', |
134 | 136 |
'xstatic.pkg.select2', |
137 |
'sorl.thumbnail', |
|
135 | 138 |
) |
136 | 139 | |
137 | 140 |
INSTALLED_APPS = tuple(plugins.register_plugins_installed_apps(INSTALLED_APPS)) |
src/authentic2/templates/authentic2/accounts.html | ||
---|---|---|
1 | 1 |
{% extends "authentic2/base-page.html" %} |
2 | 2 |
{% load i18n %} |
3 |
{% load thumbnail %} |
|
3 | 4 | |
4 | 5 |
{% block page-title %} |
5 | 6 |
{{ block.super }} - {{ view.title }} |
... | ... | |
18 | 19 |
{% for attribute in attributes %} |
19 | 20 |
<dt>{{ attribute.attribute.label|capfirst }} :</dt> |
20 | 21 |
<dd> |
21 |
{% if attribute.values|length == 1 %} |
|
22 |
{{ attribute.values.0 }} |
|
22 |
{% if attribute.attribute.kind == 'image' %} |
|
23 |
{% with request.is_secure|yesno:"https://,http://"|add:request.get_host|add:attribute.values.0 as im_url %} |
|
24 |
{% thumbnail im_url "100x100" crop="center" as thumb_im %} |
|
25 |
<img class={{ attribute.attribute.name }} src="{{ thumb_im.url }}"> |
|
26 |
{% endthumbnail %} |
|
27 |
{% endwith %} |
|
23 | 28 |
{% else %} |
24 |
<ul> |
|
25 |
{% for value in attribute.values %} |
|
26 |
<li>{{ value }}</li> |
|
27 |
{% endfor %} |
|
28 |
</ul> |
|
29 |
{% if attribute.values|length == 1 %} |
|
30 |
{{ attribute.values.0 }} |
|
31 |
{% else %} |
|
32 |
<ul> |
|
33 |
{% for value in attribute.values %} |
|
34 |
<li>{{ value }}</li> |
|
35 |
{% endfor %} |
|
36 |
</ul> |
|
37 |
{% endif %} |
|
29 | 38 |
{% endif %} |
30 | 39 |
</dd> |
31 | 40 |
{% endfor %} |
src/authentic2/templates/authentic2/accounts_edit.html | ||
---|---|---|
12 | 12 |
{% endblock %} |
13 | 13 | |
14 | 14 |
{% block content %} |
15 |
<form method="post"> |
|
15 |
{% if form.is_multipart %} |
|
16 |
<form enctype="multipart/form-data" method="post"> |
|
17 |
{% else %} |
|
18 |
<form method="post"> |
|
19 |
{% endif %} |
|
20 | ||
16 | 21 |
{% csrf_token %} |
17 | 22 |
{{ form.as_p }} |
18 | 23 |
{% if form.instance and form.instance.id %} |
src/authentic2/templates/registration/registration_form.html | ||
---|---|---|
15 | 15 | |
16 | 16 |
<h2>{{ view.title }}</h2> |
17 | 17 | |
18 |
<form method="post"> |
|
18 |
{% if form.is_multipart %} |
|
19 |
<form enctype="multipart/form-data" method="post"> |
|
20 |
{% else %} |
|
21 |
<form method="post"> |
|
22 |
{% endif %} |
|
23 | ||
19 | 24 |
{% csrf_token %} |
20 | 25 |
{{ form.as_p }} |
21 | 26 |
<button class="submit-button">{% trans 'Submit' %}</button> |
src/authentic2/urls.py | ||
---|---|---|
44 | 44 |
urlpatterns += [ |
45 | 45 |
url(r'^static/(?P<path>.*)$', serve) |
46 | 46 |
] |
47 |
urlpatterns += [ |
|
48 |
url(r'^media/(?P<path>.*)$', 'django.views.static.serve', { |
|
49 |
'document_root': settings.MEDIA_ROOT}) |
|
50 |
] |
|
47 | 51 | |
48 | 52 |
if settings.DEBUG and 'debug_toolbar' in settings.INSTALLED_APPS: |
49 | 53 |
import debug_toolbar |
src/authentic2/utils.py | ||
---|---|---|
8 | 8 |
import uuid |
9 | 9 |
import datetime |
10 | 10 |
import copy |
11 |
import fcntl |
|
12 |
import os |
|
13 |
import tempfile |
|
11 | 14 | |
12 | 15 |
from functools import wraps |
13 | 16 |
from itertools import islice, chain, count |
14 | 17 | |
15 | 18 |
from importlib import import_module |
19 |
from hashlib import md5 |
|
16 | 20 | |
17 | 21 |
from django.conf import settings |
18 | 22 |
from django.http import HttpResponseRedirect, HttpResponse |
... | ... | |
30 | 34 |
from django.template.loader import render_to_string, TemplateDoesNotExist |
31 | 35 |
from django.core.mail import send_mail |
32 | 36 |
from django.core import signing |
37 |
from django.core.files.storage import default_storage |
|
33 | 38 |
from django.core.urlresolvers import reverse, NoReverseMatch |
34 | 39 |
from django.utils.formats import localize |
35 | 40 |
from django.contrib import messages |
... | ... | |
1073 | 1078 |
if ou_value is not None: |
1074 | 1079 |
return ou_value |
1075 | 1080 |
return default |
1081 | ||
1082 | ||
1083 |
def _store_image(in_memory_image, owner_uuid, attr_label): |
|
1084 |
logger = logging.getLogger(__name__) |
|
1085 | ||
1086 |
digest = md5(in_memory_image.read()) |
|
1087 | ||
1088 |
img_id = digest.hexdigest() |
|
1089 |
img_ext = in_memory_image.image.format |
|
1090 | ||
1091 |
img_media_dir = '{label}/{oid}/'.format( |
|
1092 |
oid=owner_uuid, |
|
1093 |
label=attr_label) |
|
1094 |
img_media_path = '{imdir}/{iid}.{ext}'.format( |
|
1095 |
imdir=img_media_dir, iid=img_id, ext=img_ext) |
|
1096 | ||
1097 |
img_abs_path = default_storage.path(img_media_path) |
|
1098 |
img_abs_dir = default_storage.path(img_media_dir) |
|
1099 | ||
1100 |
try: |
|
1101 |
if not os.path.exists(img_abs_dir): |
|
1102 |
os.makedirs(img_abs_dir) |
|
1103 | ||
1104 |
with open(img_abs_path, 'wb') as f: |
|
1105 |
try: |
|
1106 |
fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB) |
|
1107 |
with tempfile.NamedTemporaryFile(mode='wb', dir=img_abs_dir, delete=False) as temp: |
|
1108 |
try: |
|
1109 |
in_memory_image.seek(0) |
|
1110 |
temp.write(in_memory_image.read()) |
|
1111 |
temp.flush() |
|
1112 |
os.rename(temp.name, img_abs_path) |
|
1113 |
except: |
|
1114 |
logger.error("Could'nt store image to {}".format(img_abs_path)) |
|
1115 |
os.unlink(temp.name) |
|
1116 |
except: |
|
1117 |
logger.error("Couldn't hold exclusive lock for file {}".format(img_abs_path)) |
|
1118 |
finally: |
|
1119 |
fcntl.lockf(f, fcntl.LOCK_UN) |
|
1120 |
except IOError: |
|
1121 |
return |
|
1122 | ||
1123 |
return img_media_path |
|
1124 | ||
1125 | ||
1126 |
def _delete_images_from_user(owner_pk, attr_label): |
|
1127 |
from .models import Attribute, AttributeValue |
|
1128 | ||
1129 |
logger = logging.getLogger(__name__) |
|
1130 |
User = get_user_model() |
|
1131 | ||
1132 |
try: |
|
1133 |
owner = User.objects.get(pk=owner_pk) |
|
1134 |
except User.DoesNotExist: |
|
1135 |
logger.error("Primary key {} doesn't match with any user.".format(owner_pk)) |
|
1136 |
return |
|
1137 | ||
1138 |
try: |
|
1139 |
attr = Attribute.objects.get(label=attr_label) |
|
1140 |
all_values = AttributeValue.objects.with_owner(owner) |
|
1141 |
values = all_values.filter(attribute=attr) |
|
1142 |
except: |
|
1143 |
logger.error("Couldn't retrieve values for Attribute {}.".format(attr_label)) |
|
1144 | ||
1145 |
for value in values: |
|
1146 |
# Direct URI <-> file location correspondence |
|
1147 |
local_file = value.content.split(default_storage.base_url)[-1] |
|
1148 |
if not local_file: |
|
1149 |
continue |
|
1150 |
media_file = default_storage.path(local_file) |
|
1151 | ||
1152 |
try: |
|
1153 |
os.remove(media_file) |
|
1154 |
value.delete() |
|
1155 |
except: |
|
1156 |
logger.error("Could'nt delete image {}".format(media_file)) |
|
1157 | ||
1158 | ||
1159 |
def image_serialize(image, owner_uuid, owner_pk, attr_label): |
|
1160 |
uri = '' |
|
1161 |
if isinstance(image, basestring): |
|
1162 |
uri = image |
|
1163 |
else: |
|
1164 |
# Discard previous user avatars |
|
1165 |
_delete_images_from_user(owner_pk, attr_label) |
|
1166 |
if image: |
|
1167 |
img_media_path = _store_image(image, owner_uuid, attr_label) |
|
1168 |
uri = os.path.join(settings.MEDIA_URL, img_media_path) |
|
1169 |
return uri |
tests/test_api.py | ||
---|---|---|
229 | 229 | |
230 | 230 |
resp = app.post_json('/api/users/', params=payload, status=status) |
231 | 231 |
if api_user.is_superuser or api_user.roles.exists(): |
232 |
assert set(['ou', 'id', 'uuid', 'is_staff', 'is_superuser', 'first_name', 'last_name', |
|
232 |
assert (set(['ou', 'id', 'uuid', 'is_staff', 'is_superuser', 'first_name', 'last_name',
|
|
233 | 233 |
'date_joined', 'last_login', 'username', 'password', 'email', 'is_active', |
234 |
'title', 'modified', 'email_verified']) == set(resp.json.keys()) |
|
234 |
'title', 'modified', 'email_verified', 'avatar_picture']) == |
|
235 |
set(resp.json.keys())) |
|
235 | 236 |
assert resp.json['first_name'] == payload['first_name'] |
236 | 237 |
assert resp.json['last_name'] == payload['last_name'] |
237 | 238 |
assert resp.json['email'] == payload['email'] |
tests/test_attribute_kinds.py | ||
---|---|---|
279 | 279 |
app.post_json('/api/users/', params=payload) |
280 | 280 |
assert qs.get().attributes.phone_number == '' |
281 | 281 |
qs.delete() |
282 | ||
283 | ||
284 |
def test_image(db, app, admin, mailoutbox): |
|
285 |
from webtest import Upload |
|
286 |
from hashlib import md5 |
|
287 | ||
288 |
Attribute.objects.create(name='cityscape_image', label='cityscape', kind='image', |
|
289 |
asked_on_registration=True) |
|
290 |
qs = User.objects.filter(first_name='John') |
|
291 | ||
292 | ||
293 |
response = app.get('/accounts/register/') |
|
294 |
form = response.form |
|
295 |
form.set('email', 'john.doe@example.com') |
|
296 |
response = form.submit().follow() |
|
297 |
assert 'john.doe@example.com' in response |
|
298 |
url = get_link_from_mail(mailoutbox[0]) |
|
299 |
response = app.get(url) |
|
300 | ||
301 |
form = response.form |
|
302 |
form.set('first_name', 'John') |
|
303 |
form.set('last_name', 'Doe') |
|
304 |
form.set('cityscape_image', Upload('/dev/null')) |
|
305 |
form.set('password1', '12345abcdA') |
|
306 |
form.set('password2', '12345abcdA') |
|
307 |
response = form.submit() |
|
308 |
assert response.pyquery.find('.form-field-error #id_cityscape_image') |
|
309 | ||
310 |
digest = md5(open('tests/cityscape.png').read()) |
|
311 |
img_id = digest.hexdigest() |
|
312 | ||
313 |
form = response.form |
|
314 |
form.set('cityscape_image', Upload('tests/cityscape.png')) |
|
315 |
form.set('password1', '12345abcdA') |
|
316 |
form.set('password2', '12345abcdA') |
|
317 |
response = form.submit() |
|
318 |
assert img_id in qs.get().attributes.cityscape_image |
|
319 | ||
320 |
app.authorization = ('Basic', (admin.username, admin.username)) |
|
321 | ||
322 |
resp = app.get('/api/users/?first_name=John&last_name=Doe') |
|
323 |
assert img_id in resp.json_body['results'][0]['cityscape_image'] |
|
324 | ||
325 |
qs.delete() |
|
326 | ||
327 |
response = app.get(url) |
|
328 |
form = response.form |
|
329 |
form.set('first_name', 'John') |
|
330 |
form.set('last_name', 'Doe') |
|
331 |
form.set('cityscape_image', None) |
|
332 |
form.set('password1', '12345abcdA') |
|
333 |
form.set('password2', '12345abcdA') |
|
334 |
response = form.submit().follow() |
|
335 |
assert qs.get().attributes.cityscape_image == None |
|
336 |
qs.delete() |
|
337 | ||
338 |
def test_images_delete_on_form_field_clearance(db, app, admin, mailoutbox): |
|
339 |
from django.core.files.storage import default_storage |
|
340 |
from hashlib import md5 |
|
341 |
from webtest import Upload |
|
342 | ||
343 |
Attribute.objects.create( |
|
344 |
name='cityscape_image', label='cityscape', kind='image', |
|
345 |
asked_on_registration=False, required=False, |
|
346 |
user_visible=True, user_editable=True) |
|
347 |
Attribute.objects.create( |
|
348 |
name='garden_image', label='garden', kind='image', |
|
349 |
asked_on_registration=False, required=False, |
|
350 |
user_visible=True, user_editable=True) |
|
351 | ||
352 |
qs = User.objects.filter(first_name='John') |
|
353 | ||
354 |
response = app.get('/accounts/register/') |
|
355 |
form = response.form |
|
356 |
form.set('email', 'john.doe@example.com') |
|
357 |
response = form.submit().follow() |
|
358 |
assert 'john.doe@example.com' in response |
|
359 |
url = get_link_from_mail(mailoutbox[0]) |
|
360 |
response = app.get(url) |
|
361 | ||
362 |
form = response.form |
|
363 |
form.set('first_name', 'John') |
|
364 |
form.set('last_name', 'Doe') |
|
365 |
form.set('password1', '12345abcdA') |
|
366 |
form.set('password2', '12345abcdA') |
|
367 |
response = form.submit() |
|
368 | ||
369 |
john = qs.get() |
|
370 |
assert john |
|
371 |
digest1 = md5(open('tests/cityscape.png').read()) |
|
372 |
img_path1 = 'cityscape/{oid}/{iid}.PNG'.format( |
|
373 |
oid=john.uuid, |
|
374 |
iid=digest1.hexdigest()) |
|
375 |
digest2 = md5(open('tests/garden.png').read()) |
|
376 |
img_path2 = 'garden/{oid}/{iid}.PNG'.format( |
|
377 |
oid=john.uuid, |
|
378 |
iid=digest2.hexdigest()) |
|
379 | ||
380 |
assert not default_storage.exists(img_path1) |
|
381 |
assert not default_storage.exists(img_path2) |
|
382 | ||
383 |
response = app.get('/accounts/edit/') |
|
384 |
form = response.form |
|
385 |
form.set('edit-profile-cityscape_image', Upload('tests/cityscape.png')) |
|
386 |
response = form.submit() |
|
387 | ||
388 |
assert default_storage.exists(img_path1) |
|
389 |
assert not default_storage.exists(img_path2) |
|
390 | ||
391 |
response = app.get('/accounts/edit/') |
|
392 |
form = response.form |
|
393 |
form.set('edit-profile-garden_image', Upload('tests/garden.png')) |
|
394 |
response = form.submit() |
|
395 | ||
396 |
assert default_storage.exists(img_path1) |
|
397 |
assert default_storage.exists(img_path2) |
|
398 | ||
399 |
response = app.get('/accounts/edit/') |
|
400 |
form = response.form |
|
401 |
form.set('edit-profile-cityscape_image-clear', True) |
|
402 |
response = form.submit() |
|
403 | ||
404 |
assert not default_storage.exists(img_path1) |
|
405 |
assert default_storage.exists(img_path2) |
|
406 | ||
407 |
response = app.get('/accounts/edit/') |
|
408 |
form = response.form |
|
409 |
form.set('edit-profile-garden_image-clear', True) |
|
410 |
response = form.submit() |
|
411 | ||
412 |
assert not default_storage.exists(img_path1) |
|
413 |
assert not default_storage.exists(img_path2) |
|
414 | ||
415 | ||
416 |
def test_images_account_registration(db, app, admin, mailoutbox): |
|
417 |
from webtest import Upload |
|
418 | ||
419 |
Attribute.objects.create(name='cityscape_image', label='cityscape', kind='image', |
|
420 |
asked_on_registration=True) |
|
421 |
Attribute.objects.create(name='garden_image', label='garden', kind='image', |
|
422 |
asked_on_registration=True) |
|
423 |
qs = User.objects.filter(first_name='John') |
|
424 | ||
425 |
response = app.get('/accounts/register/') |
|
426 |
form = response.form |
|
427 |
form.set('email', 'john.doe@example.com') |
|
428 |
response = form.submit().follow() |
|
429 |
assert 'john.doe@example.com' in response |
|
430 |
url = get_link_from_mail(mailoutbox[0]) |
|
431 |
response = app.get(url) |
|
432 | ||
433 |
form = response.form |
|
434 |
assert form.get('cityscape_image') |
|
435 |
assert form.get('garden_image') |
|
436 |
form.set('first_name', 'John') |
|
437 |
form.set('last_name', 'Doe') |
|
438 |
form.set('cityscape_image', Upload('tests/cityscape.png')) |
|
439 |
form.set('garden_image', Upload('tests/garden.png')) |
|
440 |
form.set('password1', '12345abcdA') |
|
441 |
form.set('password2', '12345abcdA') |
|
442 |
response = form.submit() |
|
443 | ||
444 |
john = qs.get() |
|
445 |
assert john.attributes.cityscape_image |
|
446 |
assert john.attributes.garden_image |
|
447 |
john.delete() |
tests/test_manager.py | ||
---|---|---|
105 | 105 |
resp = login(app, superuser, |
106 | 106 |
reverse('a2-manager-user-detail', kwargs={'pk': simple_user.pk})) |
107 | 107 |
assert len(mail.outbox) == 0 |
108 |
resp = resp.form.submit('password_reset') |
|
109 |
assert 'A mail was sent to' in resp |
|
110 |
assert len(mail.outbox) == 1 |
|
111 |
url = get_link_from_mail(mail.outbox[0]) |
|
112 |
relative_url = url.split('testserver')[1] |
|
113 |
resp = app.get('/logout/').maybe_follow() |
|
114 |
resp = app.get(relative_url, status=200) |
|
115 |
resp.form.set('new_password1', '1234==aA') |
|
116 |
resp.form.set('new_password2', '1234==aA') |
|
117 |
resp = resp.form.submit().follow() |
|
118 |
assert str(app.session['_auth_user_id']) == str(simple_user.pk) |
|
108 |
if resp.form.enctype == u'application/x-www-form-urlencoded': |
|
109 |
resp = resp.form.submit('password_reset') |
|
110 |
assert 'A mail was sent to' in resp |
|
111 |
assert len(mail.outbox) == 1 |
|
112 |
url = get_link_from_mail(mail.outbox[0]) |
|
113 |
relative_url = url.split('testserver')[1] |
|
114 |
resp = app.get('/logout/').maybe_follow() |
|
115 |
resp = app.get(relative_url, status=200) |
|
116 |
resp.form.set('new_password1', '1234==aA') |
|
117 |
resp.form.set('new_password2', '1234==aA') |
|
118 |
resp = resp.form.submit().follow() |
|
119 |
assert str(app.session['_auth_user_id']) == str(simple_user.pk) |
|
119 | 120 | |
120 | 121 | |
121 | 122 |
def test_manager_user_detail_by_uuid(app, superuser, simple_user): |
tests/test_profile.py | ||
---|---|---|
36 | 36 |
assert attribute.get_value(simple_user) == '0123456789' |
37 | 37 | |
38 | 38 |
resp = app.get(url, status=200) |
39 |
resp.form.set('edit-profile-phone', '9876543210') |
|
40 |
resp = resp.form.submit('cancel').follow() |
|
41 |
assert attribute.get_value(simple_user) == '0123456789' |
|
39 |
if resp.form.enctype == u'application/x-www-form-urlencoded': |
|
40 |
resp.form.set('edit-profile-phone', '9876543210') |
|
41 |
resp = resp.form.submit('cancel').follow() |
|
42 |
assert attribute.get_value(simple_user) == '0123456789' |
|
42 | 43 | |
43 | 44 |
attribute.set_value(simple_user, '0123456789', verified=True) |
44 | 45 |
resp = app.get(url, status=200) |
... | ... | |
74 | 75 |
assert attribute.get_value(simple_user) == '0123456789' |
75 | 76 | |
76 | 77 |
resp = app.get(url + '?next=%s' % external_redirect_next_url, status=200) |
77 |
resp.form.set('edit-profile-phone', '1234') |
|
78 |
resp = resp.form.submit('cancel') |
|
79 |
assert_external_redirect(resp, reverse('account_management')) |
|
80 |
assert attribute.get_value(simple_user) == '0123456789' |
|
78 |
if resp.form.enctype == u'application/x-www-form-urlencoded': |
|
79 |
resp.form.set('edit-profile-phone', '1234') |
|
80 |
resp = resp.form.submit('cancel') |
|
81 |
assert_external_redirect(resp, reverse('account_management')) |
|
82 |
assert attribute.get_value(simple_user) == '0123456789' |
|
81 | 83 | |
82 | 84 | |
83 | 85 |
def test_account_edit_scopes(app, simple_user): |
... | ... | |
102 | 104 |
return set(key.split('edit-profile-')[1] |
103 | 105 |
for key in resp.form.fields.keys() if key and key.startswith('edit-profile-')) |
104 | 106 |
resp = app.get(url, status=200) |
105 |
assert get_fields(resp) == set(['first_name', 'last_name', 'phone', 'mobile', 'city', 'zipcode', 'next_url']) |
|
107 |
assert get_fields(resp) == set(['first_name', 'last_name', 'phone', 'mobile', 'city', 'zipcode', |
|
108 |
'next_url', 'avatar_picture']) |
|
106 | 109 | |
107 | 110 |
resp = app.get(url + '?scope=contact', status=200) |
108 | 111 |
assert get_fields(resp) == set(['phone', 'mobile', 'next_url']) |
tox.ini | ||
---|---|---|
47 | 47 |
httmock |
48 | 48 |
pytz |
49 | 49 |
pytest-freezegun |
50 |
pillow |
|
51 |
sorl-thumbnail |
|
50 | 52 |
commands = |
51 | 53 |
./getlasso.sh |
52 | 54 |
authentic: py.test {env:FAST:} {env:REUSEDB:} {env:COVERAGE:} {posargs:tests/ --random} |
53 |
- |