Repository: liqd/a4-opin

Issue:
After adding my birth date to my account, I can't access my account settings anymore
**URL:** 
**user:** 
**expected behaviour:** 
**behaviour:** It saves the date (as you can see in the profile), but doesn't have a way to display it in the account settings
**important screensize:**
**device & browser:** 
**Comment/Question:** Sentry: https://sentry.liqd.net/sentry/opin-prod/issues/864/
`Environment:


Request Method: GET
Request URL: http://localhost:8001/de/accounts/profile/

Django Version: 2.2.14
Python Version: 3.7.5
Installed Applications:
['home',
 'wagtail.contrib.forms',
 'wagtail.contrib.redirects',
 'wagtail.embeds',
 'wagtail.sites',
 'wagtail.users',
 'wagtail.snippets',
 'wagtail.documents',
 'wagtail.images',
 'wagtail.search',
 'wagtail.admin',
 'wagtail.core',
 'wagtail.contrib.styleguide',
 'wagtail.contrib.settings',
 'modelcluster',
 'taggit',
 'widget_tweaks',
 'easy_thumbnails',
 'parler',
 'ckeditor',
 'ckeditor_uploader',
 'background_task',
 'django.contrib.sites',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.sitemaps',
 'django.contrib.staticfiles',
 'django_countries',
 'rest_framework',
 'rules.apps.AutodiscoverRulesConfig',
 'allauth',
 'allauth.account',
 'allauth.socialaccount',
 'micawber.contrib.mcdjango',
 'adhocracy4.administrative_districts',
 'adhocracy4.images',
 'adhocracy4.forms',
 'adhocracy4.phases',
 'adhocracy4.projects',
 'adhocracy4.ratings',
 'adhocracy4.reports',
 'adhocracy4.modules',
 'adhocracy4.categories',
 'adhocracy4.comments',
 'adhocracy4.comments_async',
 'adhocracy4.maps',
 'adhocracy4.filters',
 'adhocracy4.rules',
 'adhocracy4.ckeditor',
 'adhocracy4.dashboard',
 'adhocracy4.polls',
 'adhocracy4.organisations',
 'euth.users',
 'euth.actions',
 'euth.organisations',
 'euth.projects',
 'euth.ideas',
 'euth.dashboard',
 'euth.accounts',
 'euth.memberships',
 'euth.documents',
 'euth.exports',
 'euth.offlinephases',
 'euth.maps',
 'euth.follows',
 'euth.blueprints',
 'euth.contrib',
 'euth.communitydebate',
 'debug_toolbar']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'whitenoise.middleware.WhiteNoiseMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django_cloudflare_push.middleware.push_middleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'euth.contrib.middleware.TimezoneMiddleware',
 'django.middleware.locale.LocaleMiddleware',
 'wagtail.core.middleware.SiteMiddleware',
 'wagtail.contrib.redirects.middleware.RedirectMiddleware',
 'debug_toolbar.middleware.DebugToolbarMiddleware']


Template error:
In template /home/katharina/a4-opin/euth_wagtail/templates/base.html, error at line 0
   'DateInput' object has no attribute '_format_value'
   1 : {% load base_tags i18n static url_translations wagtailuserbar %}
   2 : {% get_current_language as LANGUAGE_CODE %}
   3 : 
   4 : 
   5 : <!DOCTYPE html>
   6 : <!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="{{ request.LANGUAGE_CODE }}"> <![endif]-->
   7 : <!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8" lang="{{ request.LANGUAGE_CODE }}"> <![endif]-->
   8 : <!--[if IE 8]>         <html class="no-js lt-ie9" lang="{{ request.LANGUAGE_CODE }}"> <![endif]-->
   9 : <!--[if gt IE 8]><!--> <html class="no-js" lang="{{ request.LANGUAGE_CODE }}"> <!--<![endif]-->
   10 :     <head>


Traceback:

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/core/handlers/exception.py" in inner
  34.             response = get_response(request)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  145.                 response = self.process_exception_by_middleware(e, request)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/core/handlers/base.py" in _get_response
  143.                 response = response.render()

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/response.py" in render
  106.             self.content = self.rendered_content

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/response.py" in rendered_content
  83.         content = template.render(context, self._request)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/backends/django.py" in render
  61.             return self.template.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render
  171.                     return self._render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/test/utils.py" in instrumented_test_render
  96.     return self.nodelist.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render
  937.                 bit = node.render_annotated(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render_annotated
  904.             return self.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/loader_tags.py" in render
  150.             return compiled_parent._render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/test/utils.py" in instrumented_test_render
  96.     return self.nodelist.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render
  937.                 bit = node.render_annotated(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render_annotated
  904.             return self.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/loader_tags.py" in render
  150.             return compiled_parent._render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/test/utils.py" in instrumented_test_render
  96.     return self.nodelist.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render
  937.                 bit = node.render_annotated(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render_annotated
  904.             return self.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/loader_tags.py" in render
  62.                 result = block.nodelist.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render
  937.                 bit = node.render_annotated(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render_annotated
  904.             return self.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/loader_tags.py" in render
  62.                 result = block.nodelist.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render
  937.                 bit = node.render_annotated(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render_annotated
  904.             return self.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/defaulttags.py" in render
  209.                     nodelist.append(node.render_annotated(context))

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render_annotated
  904.             return self.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/defaulttags.py" in render
  209.                     nodelist.append(node.render_annotated(context))

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render_annotated
  904.             return self.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/defaulttags.py" in render
  309.                 return nodelist.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render
  937.                 bit = node.render_annotated(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render_annotated
  904.             return self.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/defaulttags.py" in render
  309.                 return nodelist.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render
  937.                 bit = node.render_annotated(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/template/base.py" in render_annotated
  904.             return self.render(context)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/widget_tweaks/templatetags/widget_tweaks.py" in render
  229.         return str(bounded_field)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/utils/html.py" in <lambda>
  388.     klass.__str__ = lambda self: mark_safe(klass_str(self))

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/forms/boundfield.py" in __str__
  33.         return self.as_widget()

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/widget_tweaks/templatetags/widget_tweaks.py" in as_widget
  36.         html = old_as_widget(widget, attrs, only_initial)

File "/home/katharina/a4-opin/venv/lib/python3.7/site-packages/django/forms/boundfield.py" in as_widget
  93.             renderer=self.form.renderer,

File "/home/katharina/a4-opin/euth/contrib/widgets.py" in render
  40.                     'data-default-date': self._format_value(value)

Exception Type: AttributeError at /de/accounts/profile/
Exception Value: 'DateInput' object has no attribute '_format_value'`


Discussion:

Resolving patch:
diff --git a/euth/accounts/forms.py b/euth/accounts/forms.py
--- a/euth/accounts/forms.py
+++ b/euth/accounts/forms.py
@@ -1,4 +1,5 @@
 import collections
+from datetime import date
 
 from django import forms
 from django.utils.translation import ugettext_lazy as _
@@ -59,3 +60,12 @@ def clean_username(self):
             pass
 
         return username
+
+    def clean_birthdate(self):
+        birthday = self.cleaned_data['birthdate']
+        if birthday:
+            today = date.today()
+            if birthday > today:
+                raise forms.ValidationError(_('You are not yet born. Please '
+                                              'enter a date in the past.'))
+        return birthday
diff --git a/euth/contrib/widgets.py b/euth/contrib/widgets.py
--- a/euth/contrib/widgets.py
+++ b/euth/contrib/widgets.py
@@ -23,6 +23,7 @@ class Media:
     format_index = 0
 
     def render(self, name, value, attrs=None, renderer=None):
+
         if attrs:
             format = formats.get_format(
                 self.format_key
@@ -35,10 +36,6 @@ def render(self, name, value, attrs=None, renderer=None):
             if hasattr(self, 'additional_attrs'):
                 attrs.update(self.additional_attrs)
 
-            if value:
-                attrs.update({
-                    'data-default-date': self._format_value(value)
-                })
         input = mark_safe(super().render(name, value, attrs))
 
         return input


Test patch:
diff --git a/tests/accounts/test_views.py b/tests/accounts/test_views.py
--- a/tests/accounts/test_views.py
+++ b/tests/accounts/test_views.py
@@ -1,3 +1,5 @@
+import datetime
+
 import pytest
 from django.contrib import auth
 from django.urls import reverse
@@ -20,3 +22,49 @@ def test_authenticated_user_can_view_profile(client, user, login_url):
     client.post(login_url, {'email': user.email, 'password': 'password'})
     response = client.get(url)
     assert redirect_target(response) == 'account_login'
+
+
+@pytest.mark.django_db
+def test_authenticated_user_can_update_profile(client, user, login_url,
+                                               smallImage):
+    url = reverse('account-profile')
+    client.login(username=user.email, password='password')
+    response = client.get(url)
+    assert response.status_code == 200
+
+    birthday = datetime.date(2020, 1, 1)
+    data = {'username': user.username,
+            '_avatar': smallImage,
+            'description': 'This is me.',
+            'birthdate': birthday,
+            'country': '',
+            'city': '',
+            'timezone': '',
+            'gender': '',
+            'languages': '',
+            'twitter_handle': '',
+            'facebook_handle': '',
+            'instagram_handle': '',
+            'get_notifications': ''
+            }
+    response = client.post(url, data)
+    assert response.status_code == 302
+    redirect = redirect_target(response)
+    assert redirect == 'account-profile'
+
+    response_get = client.get(reverse(redirect))
+    assert response_get.status_code == 200
+    assert response_get.context['user'].description == 'This is me.'
+    assert response_get.context['user'].birthdate == birthday
+
+    invalid_birthday = datetime.date(3000, 1, 1)
+    data['birthday'] = invalid_birthday
+
+    response = client.post(url, data)
+    assert response.status_code == 302
+    redirect = redirect_target(response)
+    assert redirect == 'account-profile'
+
+    response_get = client.get(reverse(redirect))
+    assert response_get.status_code == 200
+    assert response_get.context['user'].birthdate == birthday