├── demo
├── demo
│ ├── __init__.py
│ ├── templates
│ │ ├── userena
│ │ │ ├── base_userena.html
│ │ │ ├── emails
│ │ │ │ └── activation_email_message.html
│ │ │ ├── profile_list.html
│ │ │ ├── email_form.html
│ │ │ ├── password_form.html
│ │ │ ├── profile_form.html
│ │ │ └── profile_detail.html
│ │ ├── 404.html
│ │ ├── umessages
│ │ │ ├── message_detail.html
│ │ │ └── message_list.html
│ │ ├── 500.html
│ │ ├── static
│ │ │ └── promo.html
│ │ └── base.html
│ ├── static
│ │ ├── img
│ │ │ ├── crew.png
│ │ │ ├── grid.png
│ │ │ ├── try.png
│ │ │ ├── forkme.png
│ │ │ ├── lissy.png
│ │ │ ├── stripe.png
│ │ │ ├── trans.png
│ │ │ ├── icon-demo.png
│ │ │ ├── icon-docs.png
│ │ │ ├── userena.png
│ │ │ ├── userina.png
│ │ │ ├── arrow-down.png
│ │ │ ├── icon-browse.png
│ │ │ ├── bg-container.png
│ │ │ ├── icon-download.png
│ │ │ └── bread-and-pepper-logo.png
│ │ └── css
│ │ │ └── reset.css
│ ├── urls.py
│ ├── wsgi.py
│ ├── settings_dotcloud.py
│ └── settings.py
├── profiles
│ ├── __init__.py
│ ├── views.py
│ ├── forms.py
│ └── models.py
├── private
│ └── .gitignore
├── public
│ ├── media
│ │ └── .gitignore
│ └── static
│ │ └── .gitignore
├── dotcloud.yml
├── README.md
├── nginx.conf
├── locale
│ └── nl
│ │ └── LC_MESSAGES
│ │ ├── django.mo
│ │ └── django.po
├── wsgi.py
├── superuser.py
├── requirements.txt
├── postinstall
├── manage.py
└── createdb.py
├── .venv
├── userena
├── contrib
│ ├── __init__.py
│ └── umessages
│ │ ├── __init__.py
│ │ ├── migrations
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ │ ├── templatetags
│ │ ├── __init__.py
│ │ └── umessages_tags.py
│ │ ├── south_migrations
│ │ └── __init__.py
│ │ ├── templates
│ │ ├── umessages
│ │ │ ├── base_message.html
│ │ │ ├── message_detail.html
│ │ │ ├── message_form.html
│ │ │ └── message_list.html
│ │ └── base.html
│ │ ├── signals.py
│ │ ├── tests
│ │ ├── __init__.py
│ │ ├── test_forms.py
│ │ ├── test_managers.py
│ │ ├── test_models.py
│ │ └── test_fields.py
│ │ ├── admin.py
│ │ ├── urls.py
│ │ ├── fixtures
│ │ └── messages.json
│ │ ├── forms.py
│ │ ├── fields.py
│ │ └── managers.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ ├── clean_expired.py
│ │ └── check_permissions.py
├── migrations
│ ├── __init__.py
│ ├── 0002_auto_20170214_1529.py
│ ├── 0003_auto_20170217_1640.py
│ └── 0001_initial.py
├── south_migrations
│ ├── __init__.py
│ └── 0001_initial.py
├── tests
│ ├── profiles
│ │ ├── __init__.py
│ │ └── models.py
│ ├── __init__.py
│ ├── tests_urls.py
│ ├── tests_decorators.py
│ ├── test_backends.py
│ ├── tests_middleware.py
│ ├── test_privacy.py
│ ├── tests_utils.py
│ └── test_commands.py
├── runtests
│ ├── __init__.py
│ ├── private
│ │ └── development.db
│ ├── templates
│ │ └── 404.html
│ ├── urls.py
│ └── runtests.py
├── templates
│ ├── userena
│ │ ├── base_userena.html
│ │ ├── emails
│ │ │ ├── activation_email_subject.txt
│ │ │ ├── invitation_email_subject.txt
│ │ │ ├── confirmation_email_subject_new.txt
│ │ │ ├── confirmation_email_subject_old.txt
│ │ │ ├── confirmation_email_subject_new.html
│ │ │ ├── invitation_email_message.txt
│ │ │ ├── invitation_email_message.html
│ │ │ ├── activation_email_message.txt
│ │ │ ├── confirmation_email_message_old.txt
│ │ │ ├── confirmation_email_message_new.txt
│ │ │ ├── password_reset_message.txt
│ │ │ ├── activation_email_message.html
│ │ │ ├── confirmation_email_message_old.html
│ │ │ ├── confirmation_email_message_new.html
│ │ │ └── password_reset_message.html
│ │ ├── signout.html
│ │ ├── password_complete.html
│ │ ├── password_reset_done.html
│ │ ├── email_confirm_complete.html
│ │ ├── password_reset_complete.html
│ │ ├── email_confirm_fail.html
│ │ ├── password_reset_form.html
│ │ ├── password_reset_confirm_form.html
│ │ ├── disabled.html
│ │ ├── activate_fail.html
│ │ ├── activate_retry.html
│ │ ├── email_change_complete.html
│ │ ├── activate_retry_success.html
│ │ ├── signup_complete.html
│ │ ├── signup_form.html
│ │ ├── invite_new_user.html
│ │ ├── email_form.html
│ │ ├── signin_form.html
│ │ ├── password_form.html
│ │ ├── profile_form.html
│ │ ├── profile_list.html
│ │ ├── list_invited_users.html
│ │ └── profile_detail.html
│ └── base.html
├── locale
│ ├── bg
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── de
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── el
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── es
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── fr
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── it
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── nb
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── nl
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── pl
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── pt
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── ro
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── ru
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── tr
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── de_du
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── pt_BR
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ ├── zh_CN
│ │ └── LC_MESSAGES
│ │ │ └── django.mo
│ └── zh_TW
│ │ └── LC_MESSAGES
│ │ └── django.mo
├── apps.py
├── __init__.py
├── signals.py
├── fixtures
│ ├── profiles.json
│ └── users.json
├── admin.py
├── decorators.py
├── middleware.py
├── backends.py
├── compat.py
├── mail.py
├── settings.py
└── urls.py
├── AUTHORS
├── run_tests
├── docs
├── contrib
│ └── umessages
│ │ ├── api
│ │ ├── index.rst
│ │ ├── managers.rst
│ │ └── views.rst
│ │ └── index.rst
├── api
│ ├── backends.rst
│ ├── index.rst
│ ├── decorators.rst
│ ├── middleware.rst
│ ├── managers.rst
│ ├── utils.rst
│ ├── models.rst
│ ├── forms.rst
│ └── views.rst
├── commands.rst
├── signals.rst
├── index.rst
└── Makefile
├── setup.cfg
├── .gitignore
├── MANIFEST.in
├── .coveragerc
├── README.md
├── .travis.yml
├── tox.ini
├── LICENSE
├── CREDITS
├── setup.py
└── UPDATES.md
/demo/demo/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.venv:
--------------------------------------------------------------------------------
1 | django-userena
2 |
--------------------------------------------------------------------------------
/demo/profiles/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/userena/contrib/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/userena/management/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/userena/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/userena/south_migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/userena/tests/profiles/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/userena/management/commands/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Petar Radosevic: http://wunki.org
2 |
--------------------------------------------------------------------------------
/demo/private/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/run_tests:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | tox
4 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/public/media/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/demo/public/static/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/south_migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/userena/runtests/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
--------------------------------------------------------------------------------
/userena/templates/userena/base_userena.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
--------------------------------------------------------------------------------
/demo/dotcloud.yml:
--------------------------------------------------------------------------------
1 | www:
2 | type: python
3 | db:
4 | type: postgresql
5 |
--------------------------------------------------------------------------------
/demo/demo/templates/userena/base_userena.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/templates/umessages/base_message.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
--------------------------------------------------------------------------------
/demo/demo/static/img/crew.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/crew.png
--------------------------------------------------------------------------------
/demo/demo/static/img/grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/grid.png
--------------------------------------------------------------------------------
/demo/demo/static/img/try.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/try.png
--------------------------------------------------------------------------------
/demo/demo/static/img/forkme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/forkme.png
--------------------------------------------------------------------------------
/demo/demo/static/img/lissy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/lissy.png
--------------------------------------------------------------------------------
/demo/demo/static/img/stripe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/stripe.png
--------------------------------------------------------------------------------
/demo/demo/static/img/trans.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/trans.png
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # Userena Demo
2 |
3 | You can use this project to see how to setup Django userena for your own
4 | project.
5 |
--------------------------------------------------------------------------------
/demo/demo/static/img/icon-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/icon-demo.png
--------------------------------------------------------------------------------
/demo/demo/static/img/icon-docs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/icon-docs.png
--------------------------------------------------------------------------------
/demo/demo/static/img/userena.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/userena.png
--------------------------------------------------------------------------------
/demo/demo/static/img/userina.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/userina.png
--------------------------------------------------------------------------------
/demo/nginx.conf:
--------------------------------------------------------------------------------
1 | location /media/ { root /home/dotcloud/data ; }
2 | location /static/ { root /home/dotcloud/volatile ; }
3 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/signals.py:
--------------------------------------------------------------------------------
1 | from django.dispatch import Signal
2 |
3 | email_sent = Signal(providing_args=["msg"])
4 |
--------------------------------------------------------------------------------
/demo/demo/static/img/arrow-down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/arrow-down.png
--------------------------------------------------------------------------------
/demo/demo/static/img/icon-browse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/icon-browse.png
--------------------------------------------------------------------------------
/demo/locale/nl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/locale/nl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/demo/demo/static/img/bg-container.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/bg-container.png
--------------------------------------------------------------------------------
/demo/demo/static/img/icon-download.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/icon-download.png
--------------------------------------------------------------------------------
/userena/locale/bg/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/bg/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/de/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/de/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/el/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/el/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/es/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/es/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/fr/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/fr/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/it/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/it/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/nb/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/nb/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/nl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/nl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/pl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/pl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/pt/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/pt/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/ro/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/ro/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/ru/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/ru/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/tr/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/tr/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/runtests/private/development.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/runtests/private/development.db
--------------------------------------------------------------------------------
/demo/profiles/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 |
4 | def promo(request):
5 | return render(request, 'static/promo.html')
6 |
--------------------------------------------------------------------------------
/userena/locale/de_du/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/de_du/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/pt_BR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/pt_BR/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/zh_CN/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/zh_CN/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/userena/locale/zh_TW/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/userena/locale/zh_TW/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/demo/demo/static/img/bread-and-pepper-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chozabu/django-userena/master/demo/demo/static/img/bread-and-pepper-logo.png
--------------------------------------------------------------------------------
/userena/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class UserenaConfig(AppConfig):
5 | name = 'userena'
6 | verbose_name = 'Userena'
7 |
--------------------------------------------------------------------------------
/userena/runtests/templates/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/activation_email_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% blocktrans with site.name as site %}Your signup at {{ site }}.{% endblocktrans %}
3 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/invitation_email_subject.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% blocktrans with site.name as site %}Your invited to {{ site }}.{% endblocktrans %}
3 |
--------------------------------------------------------------------------------
/docs/contrib/umessages/api/index.rst:
--------------------------------------------------------------------------------
1 | .. _umessages-api:
2 |
3 | API Reference
4 | =============
5 |
6 | .. toctree::
7 | :maxdepth: 2
8 |
9 | managers
10 | views
11 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/confirmation_email_subject_new.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% blocktrans with site.name as site %}Email verification for {{ site }}.{% endblocktrans %}
3 |
4 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [build_sphinx]
2 | source-dir = docs/
3 | build-dir = docs/build
4 | all_files = 1
5 |
6 | [upload_sphinx]
7 | upload-dir = docs/build/html
8 |
9 | [bdist_wheel]
10 | universal = 1
11 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/confirmation_email_subject_old.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 | {% blocktrans with site.name as site %}A request to change your email address is made at {{ site }}.{% endblocktrans %}
3 |
4 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/confirmation_email_subject_new.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
4 | {% blocktrans with site.name as site %}Email verification for {{ site }}.{% endblocktrans %}
5 |
6 |
--------------------------------------------------------------------------------
/docs/api/backends.rst:
--------------------------------------------------------------------------------
1 | .. _api-backends:
2 |
3 | Backends
4 | ========
5 |
6 | .. automodule:: userena.backends
7 |
8 | Return to :ref:`api`.
9 |
10 | .. autoclass:: userena.backends.UserenaAuthenticationBackend
11 | :members:
12 |
--------------------------------------------------------------------------------
/docs/api/index.rst:
--------------------------------------------------------------------------------
1 | .. _api:
2 |
3 | API Reference
4 | =============
5 |
6 | .. toctree::
7 | :maxdepth: 2
8 |
9 | backends
10 | decorators
11 | forms
12 | managers
13 | middleware
14 | models
15 | utils
16 | views
17 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/tests/__init__.py:
--------------------------------------------------------------------------------
1 | import django
2 |
3 | if django.VERSION < (1, 6):
4 | from .test_fields import *
5 | from .test_forms import *
6 | from .test_managers import *
7 | from .test_models import *
8 | from .test_views import *
9 |
--------------------------------------------------------------------------------
/docs/api/decorators.rst:
--------------------------------------------------------------------------------
1 | .. _api-decorators:
2 |
3 | Decorators
4 | ==========
5 |
6 | .. automodule:: userena.decorators
7 |
8 | Return to :ref:`api`.
9 |
10 | secure_required
11 | ---------------
12 |
13 | .. autofunction:: userena.decorators.secure_required
14 |
--------------------------------------------------------------------------------
/demo/wsgi.py:
--------------------------------------------------------------------------------
1 | import django.core.handlers.wsgi
2 |
3 | import os
4 | import sys
5 |
6 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),'demo')))
7 | os.environ['DJANGO_SETTINGS_MODULE'] = 'demo.settings_dotcloud'
8 | application = django.core.handlers.wsgi.WSGIHandler()
9 |
--------------------------------------------------------------------------------
/docs/contrib/umessages/api/managers.rst:
--------------------------------------------------------------------------------
1 | .. _messages-api-managers:
2 |
3 | Managers
4 | ========
5 |
6 | .. automodule:: userena.contrib.umessages.managers
7 |
8 | MessageManager
9 | --------------
10 |
11 | .. autoclass:: userena.contrib.umessages.managers.MessageManager
12 | :members:
13 |
--------------------------------------------------------------------------------
/docs/api/middleware.rst:
--------------------------------------------------------------------------------
1 | .. _api-middleware:
2 |
3 | Middleware
4 | ==========
5 |
6 | .. automodule:: userena.middleware
7 |
8 | Return to :ref:`api`
9 |
10 | UserenaLocaleMiddleware
11 | -----------------------
12 |
13 | .. autoclass:: userena.middleware.UserenaLocaleMiddleware
14 | :members:
15 |
--------------------------------------------------------------------------------
/demo/superuser.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from wsgi import *
3 | from django.contrib.auth import get_user_model
4 | try:
5 | wunki = get_user_model().objects.get(username='wunki')
6 | except get_user_model().DoesNotExist:
7 | pass
8 | else:
9 | wunki.is_staff = True
10 | wunki.is_superuser = True
11 | wunki.save()
12 |
--------------------------------------------------------------------------------
/demo/demo/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load i18n %}
3 |
4 | {% block content_title %}{% trans "Page not found" %} {% endblock %}
5 |
6 | {% block content %}
7 | {% trans "Page could not be found."%}
8 | {% trans "If you think this is a bug in userena, please create an issue at Github." %}
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/userena/templates/userena/signout.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Signed out" %}{% endblock %}
5 | {% block content_title %}{% trans "You have been signed out" %}. {% endblock %}
6 |
7 | {% block content %}
8 | {% trans "You have been signed out. Till we meet again." %}
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/demo/requirements.txt:
--------------------------------------------------------------------------------
1 | # Django
2 | Django==1.4
3 |
4 | # Imaging library needed for easy_thumbnails
5 | http://effbot.org/downloads/Imaging-1.1.7.tar.gz
6 |
7 | # Thumbails used for mugshots
8 | easy-thumbnails
9 |
10 | # Django-guardian used for privacy settings
11 | django-guardian
12 |
13 | # Django-south for migrations
14 | South
15 |
16 | # Coverage for testing
17 | django_coverage
18 |
--------------------------------------------------------------------------------
/userena/templates/userena/password_complete.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Password changed" %}{% endblock %}
5 | {% block content_title %}{% trans "Your password has been changed" %}. {% endblock %}
6 |
7 | {% block content %}
8 | {% trans "From now on you can use your new password to signin" %}.
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/demo/postinstall:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # This script is to setup userena on dotcloud
4 | python createdb.py
5 | python superuser.py
6 | python manage.py syncdb --settings=demo.settings_dotcloud --noinput
7 | python manage.py migrate --settings=demo.settings_dotcloud
8 | mkdir -p /home/dotcloud/data/media /home/dotcloud/volatile/static
9 | python manage.py collectstatic --settings=demo.settings_dotcloud --noinput
10 |
--------------------------------------------------------------------------------
/docs/api/managers.rst:
--------------------------------------------------------------------------------
1 | .. _api-managers:
2 |
3 | Managers
4 | ========
5 |
6 | .. automodule:: userena.managers
7 |
8 | Return to :ref:`api`
9 |
10 | UserenaManager
11 | --------------
12 |
13 | .. autoclass:: userena.managers.UserenaManager
14 | :members:
15 |
16 | UserenaBaseProfileManager
17 | -------------------------
18 |
19 | .. autoclass:: userena.managers.UserenaBaseProfileManager
20 | :members:
21 |
--------------------------------------------------------------------------------
/userena/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Django accounts management made easy.
3 |
4 | """
5 | default_app_config = 'userena.apps.UserenaConfig'
6 |
7 | VERSION = (2, 0, 1)
8 |
9 | __version__ = '.'.join((str(each) for each in VERSION[:4]))
10 |
11 |
12 | def get_version():
13 | """
14 | Returns string with digit parts only as version.
15 |
16 | """
17 | return '.'.join((str(each) for each in VERSION[:3]))
18 |
--------------------------------------------------------------------------------
/demo/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | cwd = os.path.dirname(__file__)
7 | sys.path.append(os.path.join(os.path.abspath(os.path.dirname(cwd)), '../'))
8 |
9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
10 |
11 | from django.core.management import execute_from_command_line
12 |
13 | execute_from_command_line(sys.argv)
14 |
--------------------------------------------------------------------------------
/userena/templates/userena/password_reset_done.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Password reset email sent" %}{% endblock %}
5 | {% block content_title %}{% trans "Password reset email sent" %} {% endblock %}
6 |
7 | {% block content %}
8 | {% trans "An e-mail has been sent to you which explains how to reset your password." %}
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/userena/tests/__init__.py:
--------------------------------------------------------------------------------
1 | import django
2 |
3 | if django.VERSION < (1, 6):
4 | from .test_backends import *
5 | from .test_commands import *
6 | from .test_privacy import *
7 | from .tests_decorators import *
8 | from .tests_forms import *
9 | from .tests_managers import *
10 | from .tests_middleware import *
11 | from .tests_models import *
12 | from .tests_utils import *
13 | from .tests_views import *
--------------------------------------------------------------------------------
/userena/templates/userena/email_confirm_complete.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Email changed!" %}{% endblock %}
5 |
6 | {% block content_title %}{% trans 'New email' %} {% endblock %}
7 | {% block content %}
8 | {% blocktrans with site.name as site %}Your new email address is saved. You can continue using {{ site }} with this email.{% endblocktrans %}
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/invitation_email_message.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 | {{ inviter.username }} has invited you to {{site.name}}
3 | {% trans "To activate your account you should click on the link below:" %}
4 |
5 | {{ protocol }}://{{ site.domain }}{% url 'userena_activate_invited_user' invitation_key %}
6 |
7 | {% trans "Serving you is our pleasure!" %}
8 |
9 | {% trans "Sincerely" %},
10 | {{ site.name }}
11 | {% endautoescape %}
12 |
--------------------------------------------------------------------------------
/userena/templates/userena/password_reset_complete.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Password reset" %}{% endblock %}
5 | {% block content_title %}{% trans "Your password has been reset" %} {% endblock %}
6 | {% block content %}
7 | {% url 'userena_signin' as signin_url %}
8 | {% blocktrans %}You can now signin with your new password.{% endblocktrans %}
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/userena/management/commands/clean_expired.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 |
3 | from userena.models import UserenaSignup
4 |
5 | class Command(BaseCommand):
6 | """
7 | Search for users that still haven't verified their email after
8 | ``USERENA_ACTIVATION_DAYS`` and delete them.
9 |
10 | """
11 | help = 'Deletes expired users.'
12 | def handle(self, *args, **kwargs):
13 | users = UserenaSignup.objects.delete_expired_users()
14 |
--------------------------------------------------------------------------------
/userena/templates/userena/email_confirm_fail.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Email verification failed." %}{% endblock %}
5 | {% block content_title %}{% trans "Your email could not be verified..." %} {% endblock %}
6 |
7 | {% block content %}
8 | {% trans "Your email could not be verified..." %}
9 | {% trans "You could try changing it again in your account settings." %}
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/userena/templates/userena/password_reset_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Reset password" %}{% endblock %}
5 |
6 | {% block content %}
7 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/userena/templates/userena/password_reset_confirm_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Reset password" %}{% endblock %}
5 |
6 | {% block content %}
7 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/userena/templates/userena/disabled.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Disabled account" %}{% endblock %}
5 | {% block content_title %}{% trans "Your account has been disabled" %} {% endblock %}
6 | {% block content %}
7 | {% trans "It seems your account has been disabled." %}
8 | {% trans "If you feel that injustice has been done to you, feel free to contact the administrators to find out why" %}
9 | {% endblock %}
10 |
--------------------------------------------------------------------------------
/docs/api/utils.rst:
--------------------------------------------------------------------------------
1 | .. _api-utils:
2 |
3 | Utils
4 | =====
5 |
6 | .. automodule:: userena.utils
7 |
8 | get_gravatar
9 | ------------
10 |
11 | .. autofunction:: userena.utils.get_gravatar
12 |
13 | signin_redirect
14 | ---------------
15 |
16 | .. autofunction:: userena.utils.signin_redirect
17 |
18 | generate_sha1
19 | -------------
20 |
21 | .. autofunction:: userena.utils.generate_sha1
22 |
23 | get_profile_model
24 | -----------------
25 |
26 | .. autofunction:: userena.utils.get_profile_model
27 |
28 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/templates/umessages/message_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'umessages/base_message.html' %}
2 | {% load i18n %}
3 |
4 | {% block content_title %}{% blocktrans %}Conversation with {{ recipient }}{% endblocktrans %} {% endblock %}
5 |
6 | {% block content %}
7 |
8 |
16 | {% endblock %}
17 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/templates/umessages/message_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'umessages/base_message.html' %}
2 | {% load i18n %}
3 |
4 | {% block content_title %}{% trans "Compose" %} {% endblock %}
5 |
6 | {% block content %}
7 |
15 | {% endblock %}
16 |
--------------------------------------------------------------------------------
/userena/signals.py:
--------------------------------------------------------------------------------
1 | from django.dispatch import Signal
2 |
3 | signup_complete = Signal(providing_args=["user",])
4 | activation_complete = Signal(providing_args=["user",])
5 | confirmation_complete = Signal(providing_args=["user","old_email"])
6 | password_complete = Signal(providing_args=["user",])
7 | email_change = Signal(providing_args=["user","prev_email","new_email"])
8 | profile_change = Signal(providing_args=["user",])
9 | account_signin = Signal(providing_args=["user",])
10 | account_signout = Signal(providing_args=["user",])
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Setup.py
2 | *.egg
3 | *egg-info
4 | # Documentation
5 | _build
6 | build
7 | .Python
8 |
9 | # Virtual environment
10 | bin/
11 | lib/
12 | lib64
13 | include/
14 | src/
15 | dist/
16 |
17 | # Certificates
18 | certs/
19 |
20 | # Production server
21 | logs/
22 | pip-log.txt
23 |
24 | # Testing
25 | .coverage
26 | .tox
27 |
28 | # Demo project
29 | demo/demo_project.db
30 | demo/settings_production.py
31 | demo/media/admin
32 |
33 | # Compiled
34 | *.pyc
35 |
36 | # Temp vim files
37 | *.swp
38 |
39 | # Backup files
40 | *~
41 |
--------------------------------------------------------------------------------
/userena/templates/userena/activate_fail.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Account activation failed." %}{% endblock %}
5 | {% block content_title %}{% trans "Your account could not be activated..." %} {% endblock %}
6 |
7 | {% block content %}
8 | {% trans "Your account could not be activated. This could be because your activation link has aged." %}
9 | {% trans "Please try signing up again or contact us if you think something went wrong." %}
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/userena/templates/userena/activate_retry.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 |
5 | {% block title %}{% trans "Account activation failed." %}{% endblock %}
6 | {% block content_title %}{% trans "Your account could not be activated..." %} {% endblock %}
7 |
8 | {% block content %}
9 | {% trans "Your account could not be activated because activation link is expired." %}
10 | {% trans "Request a new activation link." %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/invitation_email_message.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 |
3 |
4 | {{ inviter.username }} has invited you to {{site.name}}
5 |
6 | {% trans "To activate your account you should click on the link below:" %}
7 | {{ protocol }}://{{ site.domain }}{% url 'userena_activate_invited_user' invitation_key %}
8 |
9 |
10 |
11 | {% trans "Serving you is our pleasure!" %}
12 |
13 | {% trans "Sincerely" %},
14 | {{ site.name }}
15 |
16 |
17 |
18 | {% endautoescape %}
19 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 | include MANIFEST.in
3 | include tests.py
4 | include run_tests
5 | include pip-requirements.txt
6 | recursive-include userena/fixtures *.json
7 | recursive-include userena/templates *.html *.txt
8 | recursive-include userena/locale *.po *.mo
9 | recursive-include userena/runtests/templates *.html *.txt
10 | recursive-include demo_project *.py
11 | recursive-include demo_project/templates *.html
12 | recursive-include userena/contrib/umessages/fixtures *.json
13 | recursive-include userena/contrib/umessages/templates *.html
14 | recursive-include docs *
15 |
--------------------------------------------------------------------------------
/docs/contrib/umessages/index.rst:
--------------------------------------------------------------------------------
1 | .. _umessages:
2 |
3 | uMessages
4 | =========
5 |
6 | Userena's umesagges supplies you with iPhone like messaging system for your
7 | users.
8 |
9 |
10 | Installation
11 | ------------
12 |
13 | You install it by adding ``userena.contrib.umessages`` to your
14 | ``INSTALLED_APPS`` setting. You also need to add it to your urlconf. For
15 | example::
16 |
17 | (r'^messages/', include('userena.contrib.umessages.urls')),
18 |
19 | A ``syncdb`` later and you have a great messaging system for in your
20 | application.
21 |
22 | .. toctree::
23 | :maxdepth: 2
24 |
25 | api/index
26 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/activation_email_message.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 | {% if not without_usernames %}{% blocktrans with user.username as username %}Dear {{ username }},{% endblocktrans %}
3 | {% endif %}
4 | {% blocktrans with site.name as site %}Thank you for signing up at {{ site }}.{% endblocktrans %}
5 |
6 | {% trans "To activate your account you should click on the link below:" %}
7 |
8 | {{ protocol }}://{{ site.domain }}{% url 'userena_activate' activation_key %}
9 |
10 | {% trans "Thanks for using our site!" %}
11 |
12 | {% trans "Sincerely" %},
13 | {{ site.name }}
14 | {% endautoescape %}
15 |
--------------------------------------------------------------------------------
/userena/fixtures/profiles.json:
--------------------------------------------------------------------------------
1 | [{"pk": 1,
2 | "model": "profiles.profile",
3 | "fields": {"gender": 1,
4 | "website": "",
5 | "user": 1,
6 | "mugshot": "",
7 | "location": "Amsterdam",
8 | "privacy": "open",
9 | "language": "nl"
10 | }
11 | },
12 | {
13 | "pk": 2,
14 | "model": "profiles.profile",
15 | "fields": {"gender": 2,
16 | "website": "",
17 | "user": 2,
18 | "mugshot": "",
19 | "location": "New York",
20 | "privacy": "open",
21 | "language": "en"
22 | }
23 | }]
24 |
--------------------------------------------------------------------------------
/docs/contrib/umessages/api/views.rst:
--------------------------------------------------------------------------------
1 | .. _messages-api-views:
2 |
3 | Views
4 | =====
5 |
6 | .. automodule:: userena.contrib.umessages.views
7 |
8 | MessageListView
9 | ---------------
10 |
11 | .. autofunction:: userena.contrib.umessages.views.MessageListView
12 |
13 | MessageDetailListView
14 | ---------------------
15 |
16 | .. autofunction:: userena.contrib.umessages.views.MessageDetailListView
17 |
18 | message_compose
19 | ---------------
20 |
21 | .. autofunction:: userena.contrib.umessages.views.message_compose
22 |
23 | message_remove
24 | --------------
25 |
26 | .. autofunction:: userena.contrib.umessages.views.message_remove
27 |
--------------------------------------------------------------------------------
/docs/api/models.rst:
--------------------------------------------------------------------------------
1 | .. _api-models:
2 |
3 | Models
4 | ======
5 |
6 | .. automodule:: userena.models
7 |
8 | Return to :ref:`api`.
9 |
10 | upload_to_mugshot
11 | -----------------
12 |
13 | .. autofunction:: userena.models.upload_to_mugshot
14 |
15 | UserenaSignup
16 | -------------
17 |
18 | .. autoclass:: userena.models.UserenaSignup
19 | :members:
20 |
21 | UserenaBaseProfile
22 | ------------------
23 |
24 | .. autoclass:: userena.models.UserenaBaseProfile
25 | :members:
26 |
27 | UserenaLanguageBaseProfile
28 | --------------------------
29 |
30 | .. autoclass:: userena.models.UserenaLanguageBaseProfile
31 | :members:
32 |
33 |
--------------------------------------------------------------------------------
/userena/templates/userena/email_change_complete.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Email verification" %}{% endblock %}
5 | {% block content_title %}{% trans 'Confirm your new email' %} {% endblock %}
6 |
7 | {% block content %}
8 | {% blocktrans with viewed_user.userena_signup.email_unconfirmed as email %}You have received an email at {{ email }}.{% endblocktrans %}
9 | {% blocktrans with viewed_user.username as username %}To associate this email address with your account ({{ username }}) click on the link provided in this email.{% endblocktrans %}
10 | {% endblock %}
11 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/confirmation_email_message_old.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 | {% if not without_usernames %}{% blocktrans with user.username as username %}Dear {{ username }},{% endblocktrans %}
3 | {% endif %}
4 | {% blocktrans with site.name as site %}There was a request to change your email address at {{ site }}.{% endblocktrans %}
5 |
6 | {% blocktrans %}An email has been send to {{ new_email }} which contains a verification link. Click on the link in this email to activate it.{% endblocktrans %}
7 |
8 | {% trans "Thanks for using our site!" %}
9 |
10 | {% trans "Sincerely" %},
11 | {{ site.name }}
12 | {% endautoescape %}
13 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/confirmation_email_message_new.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 | {% if not without_usernames %}{% blocktrans with user.username as username %}Dear {{ username }},{% endblocktrans %}
3 | {% endif %}
4 | {% blocktrans with site.name as site %}You requested a change of your email address at {{ site }}.{% endblocktrans %}
5 |
6 |
7 | {% trans "Please confirm this email address by clicking on the link below:" %}
8 |
9 | {{ protocol }}://{{ site.domain }}{% url 'userena_email_confirm' confirmation_key %}
10 |
11 |
12 | {% trans "Thanks for using our site!" %}
13 |
14 | {% trans "Sincerely" %},
15 | {{ site.name }}
16 | {% endautoescape %}
17 |
--------------------------------------------------------------------------------
/userena/templates/userena/activate_retry_success.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 |
5 | {% block title %}{% trans "Account re-activation succeeded." %}{% endblock %}
6 | {% block content_title %}{% trans "Account re-activation" %} {% endblock %}
7 |
8 | {% block content %}
9 | {% blocktrans %}You requested a new activation of your account..{% endblocktrans %}
10 | {% blocktrans %}You have been sent an e-mail with an activation link to the supplied email.{% endblocktrans %}
11 | {% blocktrans %}We will store your signup information for {{ userena_activation_days }} days on our server. {% endblocktrans %}
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/.coveragerc:
--------------------------------------------------------------------------------
1 | # .coveragerc to control coverage.py
2 | [run]
3 | branch = True
4 |
5 | [report]
6 | # Regexes for lines to exclude from consideration
7 | exclude_lines =
8 | # Have to re-enable the standard pragma
9 | pragma: no cover
10 |
11 | # Don't complain about missing debug-only code:
12 | def __repr__
13 | if self\.debug
14 |
15 | # Don't complain if tests don't hit defensive assertion code:
16 | raise AssertionError
17 | raise NotImplementedError
18 |
19 | # Don't complain if non-runnable code isn't run:
20 | if 0:
21 | if __name__ == .__main__.:
22 |
23 | omit =
24 | */runtests/*
25 | */migrations/*
26 | */tests/*
27 | */south_migrations/*
28 |
29 | ignore_errors = True
30 |
--------------------------------------------------------------------------------
/userena/templates/base.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 | {% block title %}Userena{% endblock %}
8 | {% block meta %}{% endblock %}
9 |
10 |
11 |
12 | {% block body %}
13 |
14 | {% block content_title %}{% endblock %}
15 |
16 |
17 | {% block content %}{% endblock %}
18 |
19 | {% endblock %}
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/templates/base.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 | {% block title %}Userena{% endblock %}
8 | {% block meta %}{% endblock %}
9 |
10 |
11 |
12 | {% block body %}
13 |
14 | {% block content_title %}{% endblock %}
15 |
16 |
17 | {% block content %}{% endblock %}
18 |
19 | {% endblock %}
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/password_reset_message.txt:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 | {% blocktrans %}You're receiving this e-mail because you requested a password reset
3 | for your user account at {{ site_name }}{% endblocktrans %}.
4 |
5 | {% trans "Please go to the following page and choose a new password:" %}
6 | {% block reset_link %}
7 | {{ protocol }}://{{ domain }}{% url 'userena_password_reset_confirm' uid token %}
8 | {% endblock %}
9 |
10 | {% if not without_usernames %}{% blocktrans with user.username as username %}
11 | Your username, in case you've forgotten: {{ username }}
12 | {% endblocktrans %}
13 | {% endif %}
14 | {% trans "Thanks for using our site!" %}
15 |
16 | {% trans "Sincerely" %},
17 | {{ site_name }}
18 | {% endautoescape %}
19 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/activation_email_message.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 |
3 |
4 | {% if not without_usernames %}{% blocktrans with user.username as username %}Dear {{ username }},
{% endblocktrans %}{% endif %}
5 | {% blocktrans with site.name as site %}Thank you for signing up at {{ site }}.
{% endblocktrans %}
6 |
7 | {% trans "To activate your account you should click on the link below:" %}
8 | {{ protocol }}://{{ site.domain }}{% url 'userena_activate' activation_key %}
9 |
10 |
11 | {% trans "Thanks for using our site!" %}
12 | {% trans "Sincerely" %},
13 | {{ site.name }}
14 |
15 |
16 |
17 | {% endautoescape %}
18 |
--------------------------------------------------------------------------------
/userena/templates/userena/signup_complete.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Signup almost done!" %}{% endblock %}
5 |
6 | {% block content_title %}{% trans "Signup" %} {% endblock %}
7 |
8 | {% block content %}
9 | {% trans "Thank you for signing up with us!" %}
10 |
11 | {% if userena_activation_required %}
12 | {% blocktrans %}You have been sent an e-mail with an activation link to the supplied email.{% endblocktrans %}
13 | {% blocktrans %}We will store your signup information for {{ userena_activation_days }} days on our server. {% endblocktrans %}
14 | {% else %}
15 | {% blocktrans %}You can now use the supplied credentials to signin.{% endblocktrans %}
16 | {% endif %}
17 | {% endblock %}
18 |
--------------------------------------------------------------------------------
/demo/demo/templates/userena/emails/activation_email_message.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 |
3 |
4 | Test message
5 | {% if not without_usernames %}{% blocktrans with user.username as username %}Dear {{ username }},
{% endblocktrans %}{% endif %}
6 | {% blocktrans with site.name as site %}Thank you for signing up at {{ site }}.
{% endblocktrans %}
7 |
8 | {% trans "To activate your account you should click on the link below:" %}
9 | {{ protocol }}://{{ site.domain }}{% url 'userena_activate' activation_key %}
10 |
11 |
12 | {% trans "Thanks for using our site!" %}
13 | {% trans "Sincerely" %},
14 | {{ site.name }}
15 |
16 |
17 |
18 | {% endautoescape %}
19 |
--------------------------------------------------------------------------------
/docs/commands.rst:
--------------------------------------------------------------------------------
1 | .. _commands:
2 |
3 | Commands.
4 | =========
5 |
6 | Userena currently comes with two commands. ``cleanexpired`` for cleaning out
7 | the expired users and ``check_permissions`` for checking the correct
8 | permissions needed by userena.
9 |
10 | Clean expired
11 | --------------
12 |
13 | Search for users that still haven't verified their e-mail address after
14 | ``USERENA_ACTIVATION_DAYS`` and delete them. Run by ::
15 |
16 | ./manage.py clean_expired
17 |
18 | Check permissions
19 | -----------------
20 |
21 | This command shouldn't be run as a cronjob. This is only for emergency
22 | situations when some permissions are not correctly set for users. For example
23 | when userena get's implemented in an already existing project. Run by ::
24 |
25 | ./manage.py check_permissions
26 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/confirmation_email_message_old.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 |
3 |
4 | {% if not without_usernames %}{% blocktrans with user.username as username %}Dear {{ username }},{% endblocktrans %}
{% endif %}
5 |
6 | {% blocktrans with site.name as site %}There was a request to change your email address at {{ site }}.{% endblocktrans %}
7 |
8 |
9 |
10 | {% blocktrans %}An email has been send to {{ new_email }} which contains a verification link. Click on the link in this email to activate it.{% endblocktrans %}
11 |
12 |
13 | {% trans "Thanks for using our site!" %}
14 |
15 |
16 | {% trans "Sincerely" %},
17 | {{ site.name }}
18 |
19 |
20 |
21 | {% endautoescape %}
22 |
--------------------------------------------------------------------------------
/demo/demo/templates/umessages/message_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'umessages/base_message.html' %}
2 | {% load i18n %}
3 |
4 | {% block content_title %}Conversation with {{ recipient }} {% endblock %}
5 |
6 | {% block content %}
7 | {% trans "Reply" %}
8 |
9 | {% for message in message_list %}
10 |
11 |
12 |
13 | {{ message }}
14 |
{{ message.sent_at }}
15 |
16 |
17 |
18 | {% endfor %}
19 |
20 | {% endblock %}
21 |
--------------------------------------------------------------------------------
/docs/api/forms.rst:
--------------------------------------------------------------------------------
1 | .. _api-forms:
2 |
3 | Forms
4 | =====
5 |
6 | .. automodule:: userena.forms
7 |
8 | Return to :ref:`api`.
9 |
10 | SignupForm
11 | ----------
12 |
13 | .. autoclass:: userena.forms.SignupForm
14 | :members:
15 |
16 | SignupFormOnlyEmail
17 | -------------------
18 |
19 | .. autoclass:: userena.forms.SignupFormOnlyEmail
20 | :members:
21 |
22 | SignupFormTos
23 | -------------
24 |
25 | .. autoclass:: userena.forms.SignupFormTos
26 | :members:
27 |
28 | AuthenticationForm
29 | ------------------
30 |
31 | .. autoclass:: userena.forms.AuthenticationForm
32 | :members:
33 |
34 | ChangeEmailForm
35 | ---------------
36 |
37 | .. autoclass:: userena.forms.ChangeEmailForm
38 | :members:
39 |
40 | EditProfileForm
41 | ---------------
42 |
43 | .. autoclass:: userena.forms.EditProfileForm
44 | :members:
45 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/confirmation_email_message_new.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 |
3 |
4 | {% if not without_usernames %}{% blocktrans with user.username as username %}Dear {{ username }},
{% endblocktrans %}{% endif %}
5 |
6 | {% blocktrans with site.name as site %}You requested a change of your email address at {{ site }}.{% endblocktrans %}
7 |
8 |
9 | {% trans "Please confirm this email address by clicking on the link below:" %}
10 | {{ protocol }}://{{ site.domain }}{% url 'userena_email_confirm' confirmation_key %}
11 |
12 |
13 | {% trans "Thanks for using our site!" %}
14 | {% trans "Sincerely" %},
15 | {{ site_name }}
16 |
17 |
18 |
19 | {% endautoescape %}
20 |
--------------------------------------------------------------------------------
/userena/templates/userena/signup_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Signup" %}{% endblock %}
5 |
6 | {% block content %}
7 |
29 | {% endblock %}
30 |
--------------------------------------------------------------------------------
/userena/templates/userena/invite_new_user.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Invite New User" %}{% endblock %}
5 |
6 | {% block content %}
7 |
29 | {% endblock %}
30 |
--------------------------------------------------------------------------------
/userena/templates/userena/emails/password_reset_message.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}{% autoescape off %}
2 |
3 |
4 |
5 | {% blocktrans %}You're receiving this e-mail because you requested a password reset
6 | for your user account at {{ site_name }}{% endblocktrans %}.
7 |
8 |
9 | {% trans "Please go to the following page and choose a new password:" %}
10 | {% block reset_link %}
11 | {{ protocol }}://{{ domain }}{% url 'userena_password_reset_confirm' uid token %}
12 | {% endblock %}
13 |
14 | {% if not without_usernames %}
15 | {% blocktrans with user.username as username %}Your username, in case you've forgotten: {{ username }}{% endblocktrans %}
16 | {% endif %}
17 |
18 | {% trans "Thanks for using our site!" %}
19 | {% trans "Sincerely" %},
20 | {{ site_name }}
21 |
22 |
23 |
24 | {% endautoescape %}
25 |
--------------------------------------------------------------------------------
/userena/tests/tests_urls.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from django.urls import reverse, NoReverseMatch
3 |
4 |
5 | class UserenaUrlsTests(TestCase):
6 | """ Test url resolve """
7 |
8 | def test_resolve_email_with_plus_url(self):
9 | username = 'foo+bar@example.com'
10 | test_urls = [
11 | 'userena_signup_complete',
12 | 'userena_email_change',
13 | 'userena_email_change_complete',
14 | 'userena_email_confirm_complete',
15 | 'userena_disabled',
16 | 'userena_password_change',
17 | 'userena_password_change_complete',
18 | 'userena_profile_edit',
19 | 'userena_profile_detail',
20 | ]
21 | for url_name in test_urls:
22 | try:
23 | reverse(url_name, kwargs={'username': username})
24 | except NoReverseMatch as ex:
25 | self.failed(ex)
26 |
--------------------------------------------------------------------------------
/userena/migrations/0002_auto_20170214_1529.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.5 on 2017-02-14 11:59
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 | import userena.managers
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | ('userena', '0001_initial'),
13 | ]
14 |
15 | operations = [
16 | migrations.AlterModelManagers(
17 | name='userenasignup',
18 | managers=[
19 | ('objects', userena.managers.UserenaManager()),
20 | ],
21 | ),
22 | migrations.AlterField(
23 | model_name='userenasignup',
24 | name='email_unconfirmed',
25 | field=models.EmailField(blank=True, help_text='Temporary email address when the user requests an email change.', max_length=254, verbose_name='unconfirmed email address'),
26 | ),
27 | ]
28 |
--------------------------------------------------------------------------------
/userena/migrations/0003_auto_20170217_1640.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.5 on 2017-02-17 13:10
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | ('userena', '0002_auto_20170214_1529'),
12 | ]
13 |
14 | operations = [
15 | migrations.AddField(
16 | model_name='userenasignup',
17 | name='invitation_key',
18 | field=models.CharField(blank=True, max_length=40, verbose_name='invitation key'),
19 | ),
20 | migrations.AddField(
21 | model_name='userenasignup',
22 | name='invitation_status',
23 | field=models.CharField(choices=[('INV', 'Invitation Mail was sent'), ('PSWRST', 'Password was reset by user'), ('PRFEDIT', 'Profile was edited by user')], default='INV', max_length=7),
24 | ),
25 | ]
26 |
--------------------------------------------------------------------------------
/userena/tests/tests_decorators.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from django.urls import reverse
3 |
4 | from userena.utils import get_gravatar
5 | from userena import settings as userena_settings
6 |
7 | import re
8 |
9 | class DecoratorTests(TestCase):
10 | """ Test the decorators """
11 |
12 | def test_secure_required(self):
13 | """
14 | Test that the ``secure_required`` decorator does a permanent redirect
15 | to a secured page.
16 |
17 | """
18 | with self.settings(USERENA_USE_HTTPS=True):
19 | response = self.client.get(reverse('userena_signin'))
20 |
21 | # Test for the permanent redirect
22 | self.assertEqual(response.status_code, 301)
23 |
24 | # Test if the redirected url contains 'https'. Couldn't use
25 | # ``assertRedirects`` here because the redirected to page is
26 | # non-existant.
27 | self.assertTrue('https' in response.get('Location'))
28 |
--------------------------------------------------------------------------------
/userena/runtests/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url, include
2 | from django.conf import settings
3 |
4 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns
5 | from django.conf.urls.static import static
6 |
7 | from django.contrib import admin
8 | admin.autodiscover()
9 |
10 | urlpatterns = [
11 | url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
12 | url(r'^admin/', include(admin.site.urls)),
13 |
14 | # Demo Override the signup form with our own, which includes a
15 | # first and last name.
16 | # (r'^accounts/signup/$',
17 | # 'userena.views.signup',
18 | # {'signup_form': SignupFormExtra}),
19 |
20 | url(r'^accounts/', include('userena.urls')),
21 | url(r'^messages/', include('userena.contrib.umessages.urls')),
22 | url(r'^i18n/', include('django.conf.urls.i18n')),
23 | ]
24 |
25 | # Add media and static files
26 | urlpatterns += staticfiles_urlpatterns()
27 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
28 |
29 |
30 |
--------------------------------------------------------------------------------
/userena/templates/userena/email_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block content_title %}{% blocktrans with user.username as username %}Account » {{ username }}{% endblocktrans %} {% endblock %}
5 |
6 | {% block content %}
7 |
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/docs/api/views.rst:
--------------------------------------------------------------------------------
1 | .. _api-views:
2 |
3 | Views
4 | =====
5 |
6 | .. automodule:: userena.views
7 |
8 | signup
9 | ------
10 |
11 | .. autofunction:: userena.views.signup
12 |
13 | activate
14 | --------
15 |
16 | .. autofunction:: userena.views.activate
17 |
18 | email_confirm
19 | -------------
20 |
21 | .. autofunction:: userena.views.email_confirm
22 |
23 | direct_to_user_template
24 | -----------------------
25 |
26 | .. autofunction:: userena.views.direct_to_user_template
27 |
28 | signin
29 | ------
30 |
31 | .. autofunction:: userena.views.signin
32 |
33 | email_change
34 | ------------
35 |
36 | .. autofunction:: userena.views.email_change
37 |
38 | password_change
39 | ---------------
40 |
41 | .. autofunction:: userena.views.password_change
42 |
43 | profile_edit
44 | ------------
45 |
46 | .. autofunction:: userena.views.profile_edit
47 |
48 | profile_detail
49 | --------------
50 |
51 | .. autofunction:: userena.views.profile_detail
52 |
53 | profile_list
54 | ------------
55 |
56 | .. autofunction:: userena.views.profile_list
57 |
58 |
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Django Userena
2 |
3 | [](https://travis-ci.org/bread-and-pepper/django-userena)
4 | [](https://coveralls.io/r/bread-and-pepper/django-userena)
5 |
6 | Userena is a Django application that supplies your Django project with full
7 | account management. It's a fully customizable application that takes care of
8 | the signup, activation, messaging and more. It's BSD licensed, which means you
9 | can use it commercially for free!
10 |
11 | ## [Documentation](http://docs.django-userena.org/en/latest/index.html)
12 |
13 | Complete documentation about the
14 | [installation](http://docs.django-userena.org/en/latest/installation.html),
15 | [settings](http://docs.django-userena.org/en/latest/settings.html) and
16 | [F.A.Q.](http://docs.django-userena.org/en/latest/faq.html) is available on
17 | [Read the Docs](http://docs.django-userena.org/en/latest/index.html).
18 |
19 | For list of updates and changes see `UPDATES.md` file.
20 |
--------------------------------------------------------------------------------
/demo/demo/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import patterns, url, include
2 | from django.conf import settings
3 |
4 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns
5 | from django.conf.urls.static import static
6 |
7 | from django.contrib import admin
8 | admin.autodiscover()
9 |
10 | urlpatterns = [
11 | url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
12 | url(r'^admin/', include(admin.site.urls)),
13 |
14 | # Demo Override the signup form with our own, which includes a
15 | # first and last name.
16 | # (r'^accounts/signup/$',
17 | # 'userena.views.signup',
18 | # {'signup_form': SignupFormExtra}),
19 |
20 | url(r'^accounts/', include('userena.urls')),
21 | url(r'^messages/', include('userena.contrib.umessages.urls')),
22 | url(r'^$', 'profiles.views.promo', name='promo'),
23 | url(r'^i18n/', include('django.conf.urls.i18n')),
24 | ]
25 |
26 | # Add media and static files
27 | urlpatterns += staticfiles_urlpatterns()
28 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
29 |
30 |
31 |
--------------------------------------------------------------------------------
/userena/templates/userena/signin_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Signin" %}{% endblock %}
5 |
6 | {% block content %}
7 |
31 | {% endblock %}
32 |
--------------------------------------------------------------------------------
/userena/templates/userena/password_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Change password" %}{% endblock %}
5 |
6 | {% block content_title %}{% blocktrans with user.username as username %}Account » {{ username }}{% endblocktrans %} {% endblock %}
7 |
8 | {% block content %}
9 |
23 | {% endblock %}
24 |
--------------------------------------------------------------------------------
/userena/templates/userena/profile_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Account setup" %}{% endblock %}
5 |
6 | {% block content_title %}{% blocktrans with profile.user.username as username %}Account » {{ username }}{% endblocktrans %} {% endblock %}
7 |
8 | {% block content %}
9 |
10 |
24 | {% endblock %}
25 |
--------------------------------------------------------------------------------
/userena/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.contrib.auth import get_user_model
3 | from django.contrib.auth.admin import UserAdmin
4 | from django.utils.translation import ugettext as _
5 | from guardian.admin import GuardedModelAdmin
6 |
7 | from userena.models import UserenaSignup
8 | from userena import settings as userena_settings
9 | from userena.utils import get_profile_model
10 |
11 | class UserenaSignupInline(admin.StackedInline):
12 | model = UserenaSignup
13 | max_num = 1
14 |
15 | class UserenaAdmin(UserAdmin, GuardedModelAdmin):
16 | inlines = [UserenaSignupInline, ]
17 | list_display = ('username', 'email', 'first_name', 'last_name',
18 | 'is_staff', 'is_active', 'date_joined')
19 | list_filter = ('is_staff', 'is_superuser', 'is_active')
20 |
21 |
22 | if userena_settings.USERENA_REGISTER_USER:
23 | try:
24 | admin.site.unregister(get_user_model())
25 | except admin.sites.NotRegistered:
26 | pass
27 |
28 | admin.site.register(get_user_model(), UserenaAdmin)
29 |
30 | if userena_settings.USERENA_REGISTER_PROFILE:
31 | admin.site.register(get_profile_model(), GuardedModelAdmin)
32 |
--------------------------------------------------------------------------------
/docs/signals.rst:
--------------------------------------------------------------------------------
1 | .. _signals:
2 |
3 | Signals
4 | =======
5 |
6 | Userena contains a few signals which you can use in your own application if
7 | you want to have custom actions when a account get's changed. All signals are
8 | located in ``userena/signals.py`` file.
9 |
10 | signup_complete
11 | ---------------
12 |
13 | This signal get's fired when an user signs up at your site. Note: This doesn't
14 | mean that the user is activated. The signal provides you with the ``user``
15 | argument which Django's :class:`User` class.
16 |
17 | activation_complete
18 | -------------------
19 |
20 | A user has succesfully activated their account. The signal provides you with
21 | the ``user`` argument which Django's :class:`User` class.
22 |
23 | confirmation_complete
24 | ---------------------
25 |
26 | A user has succesfully changed their email. The signal provides you
27 | with the ``user`` argument which Django's :class:`User` class, and the
28 | ``old_email`` argument which is the user's old email address as a
29 | string.
30 |
31 | password_complete
32 | -----------------
33 |
34 | A user has succesfully changed their password. The signal provides you with
35 | the ``user`` argument which Django's :class:`User` class.
36 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/admin.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.utils.translation import gettext_lazy as _
3 | from django.contrib import admin
4 | from django.contrib.auth.models import Group
5 |
6 | from userena.contrib.umessages.models import Message, MessageContact, MessageRecipient
7 |
8 | class MessageRecipientInline(admin.TabularInline):
9 | """ Inline message recipients """
10 | model = MessageRecipient
11 |
12 | class MessageAdmin(admin.ModelAdmin):
13 | """ Admin message class with inline recipients """
14 | inlines = [
15 | MessageRecipientInline,
16 | ]
17 |
18 | fieldsets = (
19 | (None, {
20 | 'fields': (
21 | 'sender', 'body',
22 | ),
23 | 'classes': ('monospace' ),
24 | }),
25 | (_('Date/time'), {
26 | 'fields': (
27 | 'sender_deleted_at',
28 | ),
29 | 'classes': ('collapse', 'wide'),
30 | }),
31 | )
32 | list_display = ('sender', 'body', 'sent_at')
33 | list_filter = ('sent_at', 'sender')
34 | search_fields = ('body',)
35 |
36 | admin.site.register(Message, MessageAdmin)
37 | admin.site.register(MessageContact)
38 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url
2 | from userena.contrib.umessages import views as messages_views
3 | from django.contrib.auth.decorators import login_required
4 |
5 | urlpatterns = [
6 | url(r'^compose/$',
7 | messages_views.message_compose,
8 | name='userena_umessages_compose'),
9 |
10 | url(r'^compose/(?P[\+\.\w]+)/$',
11 | messages_views.message_compose,
12 | name='userena_umessages_compose_to'),
13 |
14 | url(r'^reply/(?P[\d]+)/$',
15 | messages_views.message_compose,
16 | name='userena_umessages_reply'),
17 |
18 | url(r'^view/(?P[\.\w]+)/$',
19 | login_required(messages_views.MessageDetailListView.as_view()),
20 | name='userena_umessages_detail'),
21 |
22 | url(r'^remove/$',
23 | messages_views.message_remove,
24 | name='userena_umessages_remove'),
25 |
26 | url(r'^unremove/$',
27 | messages_views.message_remove,
28 | {'undo': True},
29 | name='userena_umessages_unremove'),
30 |
31 | url(r'^$',
32 | login_required(messages_views.MessageListView.as_view()),
33 | name='userena_umessages_list'),
34 | ]
35 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/templates/umessages/message_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'umessages/base_message.html' %}
2 | {% load i18n umessages_tags %}
3 |
4 | {% block content_title %}{% trans "Messages" %} {% endblock %}
5 |
6 | {% block content %}
7 | {% get_unread_message_count_for user as unread_message_count %}
8 | {% blocktrans %}{{ unread_message_count }} new messages.{% endblocktrans %}
9 | {% trans "Compose" %}
10 |
11 | {% for message in message_list %}
12 |
13 | {% if message.um_from_user == user %}
14 | {{ message.um_to_user }}
15 | {% get_unread_message_count_between user and message.um_to_user as unread_between_count %}
16 | {% else %}
17 | {{ message.um_from_user }}
18 | {% get_unread_message_count_between user and message.um_from_user as unread_between_count %}
19 | {% endif %}
20 | {% blocktrans with message.latest_message as latest_message %}{{ latest_message }} ({{ unread_between_count }} new){% endblocktrans %}
21 |
22 | {% endfor %}
23 |
24 | {% endblock %}
25 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/fixtures/messages.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "pk": 1,
4 | "model": "umessages.message",
5 | "fields": {
6 | "body": "Hello from your friend",
7 | "sender_deleted_at": null,
8 | "sent_at": "2010-01-01T12:00:00.019Z",
9 | "sender": 1
10 | }
11 | },
12 | {
13 | "pk": 2,
14 | "model": "umessages.message",
15 | "fields": {
16 | "body": "Hello from your mother",
17 | "sender_deleted_at": "2010-01-01T12:00:00.019Z",
18 | "sent_at": "2010-01-01T12:00:00.019Z",
19 | "sender": 2
20 | }
21 | },
22 | {
23 | "pk": 1,
24 | "model": "umessages.messagerecipient",
25 | "fields": {
26 | "message": 1,
27 | "read_at": null,
28 | "deleted_at": null,
29 | "user": 2
30 | }
31 | },
32 | {
33 | "pk": 2,
34 | "model": "umessages.messagerecipient",
35 | "fields": {
36 | "message": 2,
37 | "read_at": "2010-01-01T12:00:00.019Z",
38 | "deleted_at": null,
39 | "user": 1
40 | }
41 | },
42 | {
43 | "pk": 1,
44 | "model": "umessages.messagecontact",
45 | "fields": {
46 | "um_from_user": 1,
47 | "um_to_user": 2,
48 | "latest_message": 2
49 | }
50 | }
51 | ]
52 |
--------------------------------------------------------------------------------
/demo/demo/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for demo_2 project.
3 |
4 | This module contains the WSGI application used by Django's development server
5 | and any production WSGI deployments. It should expose a module-level variable
6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7 | this application via the ``WSGI_APPLICATION`` setting.
8 |
9 | Usually you will have the standard Django WSGI application here, but it also
10 | might make sense to replace the whole Django WSGI application with a custom one
11 | that later delegates to the Django one. For example, you could introduce WSGI
12 | middleware here, or combine a Django application with an application of another
13 | framework.
14 |
15 | """
16 | import os
17 |
18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo_2.settings")
19 |
20 | # This application object is used by any WSGI server configured to use this
21 | # file. This includes Django's development server, if the WSGI_APPLICATION
22 | # setting points here.
23 | from django.core.wsgi import get_wsgi_application
24 | application = get_wsgi_application()
25 |
26 | # Apply WSGI middleware here.
27 | # from helloworld.wsgi import HelloWorldApplication
28 | # application = HelloWorldApplication(application)
29 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.utils.translation import ugettext_lazy as _
3 |
4 | from userena.contrib.umessages.fields import CommaSeparatedUserField
5 | from userena.contrib.umessages.models import Message, MessageRecipient
6 |
7 | import datetime
8 |
9 | class ComposeForm(forms.Form):
10 | to = CommaSeparatedUserField(label=_("To"))
11 | body = forms.CharField(label=_("Message"),
12 | widget=forms.Textarea({'class': 'message'}),
13 | required=True)
14 |
15 | def save(self, sender):
16 | """
17 | Save the message and send it out into the wide world.
18 |
19 | :param sender:
20 | The :class:`User` that sends the message.
21 |
22 | :param parent_msg:
23 | The :class:`Message` that preceded this message in the thread.
24 |
25 | :return: The saved :class:`Message`.
26 |
27 | """
28 | um_to_user_list = self.cleaned_data['to']
29 | body = self.cleaned_data['body']
30 |
31 | msg = Message.objects.send_message(sender,
32 | um_to_user_list,
33 | body)
34 |
35 | return msg
36 |
--------------------------------------------------------------------------------
/demo/demo/static/css/reset.css:
--------------------------------------------------------------------------------
1 | /* reset */
2 |
3 |
4 | html, body, div, span, applet, object, iframe,
5 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
6 | a, abbr, acronym, address, big, cite, code,
7 | del, dfn, em, font, img, ins, kbd, q, s, samp,
8 | small, strike, strong, sub, sup, tt, var,
9 | b, u, i, center,
10 | dl, dt, dd, ol, ul, li,
11 | fieldset, form, label, legend,
12 | table, caption, tbody, tfoot, thead, tr, th, td {
13 | margin: 0;
14 | padding: 0;
15 | border: 0;
16 | outline: 0;
17 | font-size: 100%;
18 | vertical-align: baseline;
19 | background: transparent;
20 | }
21 | body {
22 | line-height: 1;
23 | }
24 | ol, ul {
25 | list-style: none;
26 | }
27 | blockquote, q {
28 | quotes: none;
29 | }
30 | blockquote:before, blockquote:after,
31 | q:before, q:after {
32 | content: '';
33 | content: none;
34 | }
35 |
36 | /* remember to define focus styles! */
37 | :focus {
38 | outline: 0;
39 | }
40 |
41 | /* remember to highlight inserts somehow! */
42 | ins {
43 | text-decoration: none;
44 | }
45 | del {
46 | text-decoration: line-through;
47 | }
48 |
49 | /* tables still need 'cellspacing="0"' in the markup */
50 | table {
51 | border-collapse: collapse;
52 | border-spacing: 0;
53 | }
54 |
55 | a:focus, a:active{
56 | outline: none;
57 | }
--------------------------------------------------------------------------------
/userena/templates/userena/profile_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block content_title %}{% trans 'Profiles' %} {% endblock %}
5 |
6 | {% block content %}
7 |
16 |
17 | {% if is_paginated %}
18 |
35 | {% endif %}
36 | {% endblock %}
37 |
--------------------------------------------------------------------------------
/demo/demo/templates/userena/profile_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block content_title %}{% trans 'Profiles' %} {% endblock %}
5 |
6 | {% block content %}
7 |
16 |
17 | {% if is_paginated %}
18 |
33 | {% endif %}
34 | {% endblock %}
35 |
--------------------------------------------------------------------------------
/userena/decorators.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponsePermanentRedirect
2 | from django.utils.decorators import available_attrs
3 | from django.conf import settings
4 | from django.utils.functional import wraps
5 |
6 | from userena import settings as userena_settings
7 |
8 |
9 | def secure_required(view_func):
10 | """
11 | Decorator to switch an url from http to https.
12 |
13 | If a view is accessed through http and this decorator is applied to that
14 | view, than it will return a permanent redirect to the secure (https)
15 | version of the same view.
16 |
17 | The decorator also must check that ``USERENA_USE_HTTPS`` is enabled. If
18 | disabled, it should not redirect to https because the project doesn't
19 | support it.
20 |
21 | """
22 | def _wrapped_view(request, *args, **kwargs):
23 | if not request.is_secure():
24 | if getattr(settings, 'USERENA_USE_HTTPS', userena_settings.DEFAULT_USERENA_USE_HTTPS):
25 | request_url = request.build_absolute_uri(request.get_full_path())
26 | secure_url = request_url.replace('http://', 'https://')
27 | return HttpResponsePermanentRedirect(secure_url)
28 | return view_func(request, *args, **kwargs)
29 | return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view)
30 |
--------------------------------------------------------------------------------
/demo/demo/templates/umessages/message_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'umessages/base_message.html' %}
2 | {% load i18n umessages_tags %}
3 |
4 | {% block content_title %}{% get_unread_message_count_for user as unread_message_count %}
5 | Messages ({{ unread_message_count }} ) {% endblock %}
6 |
7 | {% block content %}
8 |
9 | + Compose message
10 |
31 | {% endblock %}
32 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python: 2.7
3 | sudo: false
4 |
5 | # note: python3.5 is not preinstalled on docker-based (i.e. sudo: false)
6 | # Travis workers at the moment of this writing, so we need to get
7 | # it via addon repository.
8 | addons:
9 | apt:
10 | sources:
11 | - deadsnakes
12 | packages:
13 | - python3.5
14 | - python3.5-dev
15 |
16 | env:
17 | - TOX_ENV=py26-django15
18 | - TOX_ENV=py26-django16
19 |
20 | - TOX_ENV=py27-django15
21 | - TOX_ENV=py27-django16
22 | - TOX_ENV=py27-django17
23 | - TOX_ENV=py27-django18
24 | - TOX_ENV=py27-django19
25 |
26 | - TOX_ENV=py32-django15
27 | - TOX_ENV=py32-django16
28 | - TOX_ENV=py32-django17
29 | - TOX_ENV=py32-django18
30 |
31 | - TOX_ENV=py33-django15
32 | - TOX_ENV=py33-django16
33 | - TOX_ENV=py33-django17
34 | - TOX_ENV=py33-django18
35 |
36 | - TOX_ENV=py34-django17
37 | - TOX_ENV=py34-django18
38 | - TOX_ENV=py34-django19
39 |
40 | - TOX_ENV=py35-django18
41 | - TOX_ENV=py35-django19
42 |
43 | - TOX_ENV=coverage
44 | install:
45 | # note: pip>=8.0.0 does not work on Python 3.2 but we want to test on py32
46 | # note: virtualenv>=14.0.0 bundles newer version of pip that does not work on
47 | # py32
48 | - pip install -U 'tox==2.2.1' 'pip<8.0.0' 'virtualenv<14.0.0'
49 | script:
50 | - tox -e $TOX_ENV
51 |
--------------------------------------------------------------------------------
/userena/middleware.py:
--------------------------------------------------------------------------------
1 | from django.utils import translation
2 | from django.core.exceptions import ObjectDoesNotExist
3 | from django.conf import settings
4 |
5 | from userena import settings as userena_settings
6 | from userena.compat import SiteProfileNotAvailable
7 | from userena.utils import get_user_profile
8 |
9 |
10 | class UserenaLocaleMiddleware(object):
11 | """
12 | Set the language by looking at the language setting in the profile.
13 |
14 | It doesn't override the cookie that is set by Django so a user can still
15 | switch languages depending if the cookie is set.
16 |
17 | """
18 | def process_request(self, request):
19 | lang_cookie = request.session.get(settings.LANGUAGE_COOKIE_NAME)
20 | if not lang_cookie:
21 | if request.user.is_authenticated():
22 | try:
23 | profile = get_user_profile(user=request.user)
24 | except (ObjectDoesNotExist, SiteProfileNotAvailable):
25 | profile = False
26 |
27 | if profile:
28 | try:
29 | lang = getattr(profile, userena_settings.USERENA_LANGUAGE_FIELD)
30 | translation.activate(lang)
31 | request.LANGUAGE_CODE = translation.get_language()
32 | except AttributeError: pass
33 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist =
3 | ; py26 support was dropped in django1.7
4 | py26-django{15,16},
5 | ; py27 still has the widest django support
6 | py27-django{15,16,17,18,19},
7 | ; py32, py33 support was officially introduced in django1.5
8 | ; py32, py33 support was dropped in django1.9
9 | py32-django{15,16,17,18},
10 | py33-django{15,16,17,18},
11 | ; py34 support was officially introduced in django1.7
12 | py34-django{17,18,19}
13 | ; py35 support was officially introduced in django1.8
14 | py35-django{18,19}
15 |
16 | [testenv]
17 | usedevelop = True
18 | deps =
19 | django{15,16}: south
20 | django{15,16}: django-guardian<1.4.0
21 | django15: django==1.5.12
22 | django16: django==1.6.11
23 | django17: django==1.7.11
24 | django18: django==1.8.7
25 | django19: django==1.9
26 | coverage: django==1.9
27 | coverage: coverage==4.0.3
28 | coverage: coveralls==1.1
29 |
30 | basepython =
31 | py35: python3.5
32 | py34: python3.4
33 | py33: python3.3
34 | py32: python3.2
35 | py27: python2.7
36 | py26: python2.6
37 |
38 | commands={envpython} userena/runtests/runtests.py userena umessages {posargs}
39 |
40 | [testenv:coverage]
41 | basepython = python2.7
42 | passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
43 | commands=
44 | coverage run --source=userena userena/runtests/runtests.py userena umessages {posargs}
45 | coveralls
46 |
--------------------------------------------------------------------------------
/userena/templates/userena/list_invited_users.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block content_title %}{% trans 'Invited Users' %} {% endblock %}
5 |
6 | {% block content %}
7 | Number of Users you could add.
8 | {{numOfRemainingInvitationTicket}}
9 |
19 |
20 | {% comment %}
21 | {% if is_paginated %}
22 |
39 | {% endif %}
40 | {% endcomment %}
41 | {% endblock %}
42 |
--------------------------------------------------------------------------------
/userena/tests/profiles/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.utils.translation import ugettext_lazy as _
3 |
4 | from userena.models import UserenaBaseProfile
5 | from userena.utils import user_model_label
6 |
7 | import datetime
8 |
9 | class Profile(UserenaBaseProfile):
10 | """ Default profile """
11 | GENDER_CHOICES = (
12 | (1, _('Male')),
13 | (2, _('Female')),
14 | )
15 |
16 | user = models.OneToOneField(user_model_label,
17 | unique=True,
18 | verbose_name=_('user'),
19 | related_name='profile')
20 |
21 | gender = models.PositiveSmallIntegerField(_('gender'),
22 | choices=GENDER_CHOICES,
23 | blank=True,
24 | null=True)
25 | website = models.URLField(_('website'), blank=True)
26 | location = models.CharField(_('location'), max_length=255, blank=True)
27 | about_me = models.TextField(_('about me'), blank=True)
28 | language = models.TextField(_('language'), blank=True)
29 |
30 | class SecondProfile(UserenaBaseProfile):
31 | user = models.OneToOneField(user_model_label,
32 | unique=True,
33 | verbose_name=_('user'),
34 | related_name='profile_second')
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010, Petar Radosevic
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are
6 | met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above
11 | copyright notice, this list of conditions and the following
12 | disclaimer in the documentation and/or other materials provided
13 | with the distribution.
14 | * Neither the name of the author nor the names of other
15 | contributors may be used to endorse or promote products derived
16 | from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/demo/demo/templates/userena/email_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block content_title %}Account » {{ account.user.username }} {% endblock %}
5 |
6 | {% block content %}
7 |
8 |
14 |
15 |
31 | {% endblock %}
32 |
--------------------------------------------------------------------------------
/demo/demo/templates/userena/password_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Change password" %}{% endblock %}
5 |
6 | {% block content_title %}Account » {{ account.user.username }} {% endblock %}
7 |
8 | {% block content %}
9 |
10 |
16 |
17 |
33 | {% endblock %}
34 |
--------------------------------------------------------------------------------
/demo/demo/templates/userena/profile_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% trans "Account setup" %}{% endblock %}
5 |
6 | {% block content_title %}Account » {{ account.user.username }} {% endblock %}
7 |
8 | {% block content %}
9 |
10 |
11 |
17 |
18 |
19 |
35 | {% endblock %}
36 |
--------------------------------------------------------------------------------
/demo/profiles/forms.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from django import forms
5 | from django.utils.translation import ugettext_lazy as _
6 |
7 | from userena.forms import SignupForm
8 |
9 | class SignupFormExtra(SignupForm):
10 | """
11 | A form to demonstrate how to add extra fields to the signup form, in this
12 | case adding the first and last name.
13 |
14 |
15 | """
16 | first_name = forms.CharField(label=_('First name'),
17 | max_length=30,
18 | required=False)
19 |
20 | last_name = forms.CharField(label=_('Last name'),
21 | max_length=30,
22 | required=False)
23 |
24 | def __init__(self, *args, **kw):
25 | """
26 |
27 | A bit of hackery to get the first name and last name at the top of the
28 | form instead at the end.
29 |
30 | """
31 | super(SignupFormExtra, self).__init__(*args, **kw)
32 | # Put the first and last name at the top
33 | new_order = self.fields.keyOrder[:-2]
34 | new_order.insert(0, 'first_name')
35 | new_order.insert(1, 'last_name')
36 | self.fields.keyOrder = new_order
37 |
38 | def save(self):
39 | """
40 | Override the save method to save the first and last name to the user
41 | field.
42 |
43 | """
44 | # First save the parent form and get the user.
45 | new_user = super(SignupFormExtra, self).save()
46 |
47 | new_user.first_name = self.cleaned_data['first_name']
48 | new_user.last_name = self.cleaned_data['last_name']
49 | new_user.save()
50 |
51 | # Userena expects to get the new user from this form, so return the new
52 | # user.
53 | return new_user
54 |
--------------------------------------------------------------------------------
/demo/profiles/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.utils.translation import ugettext_lazy as _
3 |
4 | from userena.models import UserenaLanguageBaseProfile
5 | from userena.utils import user_model_label
6 |
7 | import datetime
8 |
9 | class Profile(UserenaLanguageBaseProfile):
10 | """ Default profile """
11 | GENDER_CHOICES = (
12 | (1, _('Male')),
13 | (2, _('Female')),
14 | )
15 |
16 | user = models.OneToOneField(user_model_label,
17 | unique=True,
18 | verbose_name=_('user'),
19 | related_name='profile')
20 |
21 | gender = models.PositiveSmallIntegerField(_('gender'),
22 | choices=GENDER_CHOICES,
23 | blank=True,
24 | null=True)
25 | website = models.URLField(_('website'), blank=True)
26 | location = models.CharField(_('location'), max_length=255, blank=True)
27 | birth_date = models.DateField(_('birth date'), blank=True, null=True)
28 | about_me = models.TextField(_('about me'), blank=True)
29 |
30 | @property
31 | def age(self):
32 | if not self.birth_date: return False
33 | else:
34 | today = datetime.date.today()
35 | # Raised when birth date is February 29 and the current year is not a
36 | # leap year.
37 | try:
38 | birthday = self.birth_date.replace(year=today.year)
39 | except ValueError:
40 | day = today.day - 1 if today.day != 1 else today.day + 2
41 | birthday = self.birth_date.replace(year=today.year, day=day)
42 | if birthday > today: return today.year - self.birth_date.year - 1
43 | else: return today.year - self.birth_date.year
44 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/tests/test_forms.py:
--------------------------------------------------------------------------------
1 | #encoding:utf-8
2 | from __future__ import unicode_literals
3 |
4 | from django.contrib.auth import get_user_model
5 | from django.test import TestCase
6 |
7 | from userena.contrib.umessages.forms import ComposeForm
8 |
9 |
10 | class ComposeFormTests(TestCase):
11 | """ Test the compose form. """
12 | fixtures = ['users']
13 |
14 | def test_invalid_data(self):
15 | """
16 | Test the save method of :class:`ComposeForm`
17 |
18 | We don't need to make the ``to`` field sweat because we have done that
19 | in the ``fields`` test.
20 |
21 | """
22 | invalid_data_dicts = [
23 | # No body
24 | {'data': {'to': 'john',
25 | 'body': ''},
26 | 'error': ('body', ['This field is required.'])},
27 | ]
28 |
29 | for invalid_dict in invalid_data_dicts:
30 | form = ComposeForm(data=invalid_dict['data'])
31 | self.assertFalse(form.is_valid())
32 | self.assertEqual(form.errors[invalid_dict['error'][0]],
33 | invalid_dict['error'][1])
34 |
35 | def test_save_msg(self):
36 | """ Test valid data """
37 | valid_data = {'to': 'john, jane',
38 | 'body': 'Body'}
39 |
40 | form = ComposeForm(data=valid_data)
41 |
42 | self.assertTrue(form.is_valid())
43 |
44 | # Save the form.
45 | sender = get_user_model().objects.get(username='jane')
46 | msg = form.save(sender)
47 |
48 | # Check if the values are set correctly
49 | self.assertEqual(msg.body, valid_data['body'])
50 | self.assertEqual(msg.sender, sender)
51 | self.assertTrue(msg.sent_at)
52 |
53 | # Check recipients
54 | self.assertEqual(msg.recipients.all()[0].username, 'jane')
55 | self.assertEqual(msg.recipients.all()[1].username, 'john')
56 |
--------------------------------------------------------------------------------
/userena/backends.py:
--------------------------------------------------------------------------------
1 | import django.core.validators
2 | from django.contrib.auth import get_user_model
3 | from django.contrib.auth.backends import ModelBackend
4 |
5 |
6 | class UserenaAuthenticationBackend(ModelBackend):
7 | """
8 | Custom backend because the user must be able to supply a ``email`` or
9 | ``username`` to the login form.
10 |
11 | """
12 | def authenticate(self, identification, password=None, check_password=True):
13 | """
14 | Authenticates a user through the combination email/username with
15 | password.
16 |
17 | :param identification:
18 | A string containing the username or e-mail of the user that is
19 | trying to authenticate.
20 |
21 | :password:
22 | Optional string containing the password for the user.
23 |
24 | :param check_password:
25 | Boolean that defines if the password should be checked for this
26 | user. Always keep this ``True``. This is only used by userena at
27 | activation when a user opens a page with a secret hash.
28 |
29 | :return: The signed in :class:`User`.
30 |
31 | """
32 | User = get_user_model()
33 | try:
34 | django.core.validators.validate_email(identification)
35 | try: user = User.objects.get(email__iexact=identification)
36 | except User.DoesNotExist: return None
37 | except django.core.validators.ValidationError:
38 | try: user = User.objects.get(username__iexact=identification)
39 | except User.DoesNotExist: return None
40 | if check_password:
41 | if user.check_password(password):
42 | return user
43 | return None
44 | else: return user
45 |
46 | def get_user(self, user_id):
47 | User = get_user_model()
48 | try: return User.objects.get(pk=user_id)
49 | except User.DoesNotExist:
50 | return None
51 |
--------------------------------------------------------------------------------
/demo/demo/templates/userena/profile_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{{ profile.user.username }}'s {% trans "profile" %}.{% endblock %}
5 | {% block content_title %}{{ profile.user.username }} {% if profile.user.get_full_name %}({{ profile.user.get_full_name }}){% endif %} {% endblock %}
6 |
7 | {% block content %}
8 |
9 |
10 | {# Dirty hack. Will use django-guardian in the future. #}
11 | {% if user.username == profile.user.username %}
12 |
18 | {% endif %}
19 |
20 |
21 |
22 | {% if profile.user.get_full_name %}
23 |
{% trans "Name" %} {{ profile.user.get_full_name }}
24 | {% endif %}
25 | {% if profile.age != 0 %}
26 |
{% trans "Age" %} {{ profile.age }}
27 | {% endif %}
28 | {% if profile.website %}
29 |
{% trans "Website" %} {{ profile.website }}
30 | {% endif %}
31 | {% if profile.location %}
32 |
{% trans "Location" %} {{ profile.location }}
33 | {% endif %}
34 | {% if profile.about_me %}
35 |
{% trans "About me" %} {{ profile.about_me }}
36 | {% endif %}
37 |
38 |
39 |
40 | {% endblock %}
41 |
--------------------------------------------------------------------------------
/userena/management/commands/check_permissions.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 | from django.utils.encoding import smart_text
3 |
4 | from userena.compat import make_options
5 | from userena.models import UserenaSignup
6 |
7 | arguments = (
8 | ('--no-output', {
9 | 'action': 'store_false',
10 | 'dest': 'output',
11 | 'default': True,
12 | 'help': 'Hide informational output.'
13 | }),
14 | ('--test', {
15 | 'action': 'store_true',
16 | 'dest': 'test',
17 | 'default': False,
18 | 'help': "Displays that it's testing management command. Don't use it yourself."
19 | }),
20 | )
21 |
22 |
23 | class Command(BaseCommand):
24 | """
25 | For unknown reason, users can get wrong permissions.
26 | This command checks that all permissions are correct.
27 |
28 | """
29 | option_list = make_options(arguments)
30 |
31 | def add_arguments(self, parser):
32 | for arg, attrs in arguments:
33 | parser.add_argument(arg, **attrs)
34 |
35 |
36 | help = 'Check that user permissions are correct.'
37 | def handle(self, **options):
38 | permissions, users, warnings = UserenaSignup.objects.check_permissions()
39 | output = options.pop("output")
40 | test = options.pop("test")
41 | if test:
42 | self.stdout.write(40 * ".")
43 | self.stdout.write("\nChecking permission management command. Ignore output..\n\n")
44 | if output:
45 | for p in permissions:
46 | self.stdout.write("Added permission: %s\n" % p)
47 |
48 | for u in users:
49 | self.stdout.write("Changed permissions for user: %s\n" % smart_text(u, encoding='utf-8', strings_only=False))
50 |
51 | for w in warnings:
52 | self.stdout.write("WARNING: %s\n" %w)
53 |
54 | if test:
55 | self.stdout.write("\nFinished testing permissions command.. continuing..\n")
56 |
--------------------------------------------------------------------------------
/demo/demo/templates/500.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
4 |
5 |
6 | Userena crashed!
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Userena crashed
20 |
21 |
Sorry for the inconvenience.
22 |
23 |
Please take the time to report an issue at Github so it doesn't happen again.
24 |
25 |
26 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/CREDITS:
--------------------------------------------------------------------------------
1 | James Bennett: http://b-list.org (for creating django-registration)
2 | Grigoriy Petukhov: http://web-brains.com (for creating django-account)
3 | Lukasz Balcerzak: http://djangopeople.net/lukaszbalcerzak/ (for django-guardian and Polish translations)
4 | George Notaras: http://www.g-loaded.eu (for making userena 2.4 compatible)
5 | Jannis Leidel: http://enn.io (for fixing installation script)
6 | Guillaume Esquevin: http://www.platypus-creation.com (for improving translations)
7 | Aram Dulyan: http://interaction.net.au/ (for making userena compatible with Django 1.5 user model)
8 | Michał Jaworski: https://github.com/swistakm (for adding tox testing and updating Userena to work with Django 1.6.)
9 | Adi Jörg Sieker: http://sieker.io/ (for updating documentation and profile admin)
10 | Joel Bremson: https://github.com/jbremson (for updating documentation)
11 | Boris Savelev: https://github.com/bsavelev (for fixing some signal issues and allowing '@' in userena profile urls)
12 | nachouve: https://github.com/nachouve (for updating documentation)
13 | Ethno-urban: https://github.com/Ethno-urban (for updating documentation)
14 | Fabio Souto: http://fabiosouto.me/ (for updating documentation)
15 | ChangeSomeCode: https://github.com/ChangeSomeCode (for adding support for Django 1.7)
16 | Ghassen Telmoudi: https://github.com/pyghassen (for work on Python 3 support)
17 | Leonardo Orozco: https://github.com/leonardoo (for work on Python 3 support and multiple improvements)
18 | Guillaume Thomas: https://github.com/gtnx (for various fixes)
19 | Morten W. Hansen: https://github.com/mortenwh (for various fixes and translations)
20 | wordstospend: https://github.com/wordstospend (for updating documentation)
21 | Žan Anderle: https://github.com/zanderle (for improvements and compatibility fixes)
22 | Alex Willmer: https://github.com/moreati (for fixing django 1.4 builds)
23 | Andres Vargas: https://github.com/zodman (for fixing translations)
24 | Angel Ramboi: https://github.com/aramboi (for Romanian translations)
25 | smlz: https://github.com/smlz (for tremendous work in adding Django 1.7 and Django 1.8 support)
26 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/tests/test_managers.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from django.test import TestCase
3 |
4 | from userena.contrib.umessages.models import (Message, MessageContact,
5 | MessageRecipient)
6 |
7 | User = get_user_model()
8 |
9 |
10 | class MessageManagerTests(TestCase):
11 | fixtures = ['users', 'messages']
12 |
13 | def test_get_conversation(self):
14 | """ Test that the conversation is returned between two users """
15 | user_1 = User.objects.get(pk=1)
16 | user_2 = User.objects.get(pk=2)
17 |
18 | messages = Message.objects.get_conversation_between(user_1, user_2)
19 |
20 | class MessageRecipientManagerTest(TestCase):
21 | fixtures = ['users', 'messages']
22 |
23 | def test_count_unread_messages_for(self):
24 | """ Test the unread messages count for user """
25 | jane = User.objects.get(pk=2)
26 |
27 | # Jane has one unread message from john
28 | unread_messages = MessageRecipient.objects.count_unread_messages_for(jane)
29 |
30 | self.assertEqual(unread_messages, 1)
31 |
32 | def test_count_unread_messages_between(self):
33 | """ Test the unread messages count between two users """
34 | john = User.objects.get(pk=1)
35 | jane = User.objects.get(pk=2)
36 |
37 | # Jane should have one unread message from john
38 | unread_messages = MessageRecipient.objects.count_unread_messages_between(jane, john)
39 |
40 | self.assertEqual(unread_messages, 1)
41 |
42 | class MessageContactManagerTest(TestCase):
43 | fixtures = ['users', 'messages']
44 |
45 | def test_get_contacts_for(self):
46 | """ Test if the correct contacts are returned """
47 | john = User.objects.get(pk=1)
48 | contacts = MessageContact.objects.get_contacts_for(john)
49 |
50 | # There is only one contact for John, and that's Jane.
51 | self.assertEqual(len(contacts), 1)
52 |
53 | jane = User.objects.get(pk=2)
54 | self.assertEqual(contacts[0].um_to_user,
55 | jane)
56 |
57 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/tests/test_models.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from django.test import TestCase
3 |
4 | from userena.contrib.umessages.models import Message, MessageRecipient, MessageContact
5 | from userena.utils import truncate_words
6 |
7 | User = get_user_model()
8 |
9 |
10 | class MessageContactTests(TestCase):
11 | fixtures = ['users', 'messages']
12 |
13 | def test_string_formatting(self):
14 | """ Test the human representation of a message """
15 | contact = MessageContact.objects.get(pk=1)
16 | correct_format = "john and jane"
17 | self.assertEqual(contact.__str__(),
18 | correct_format)
19 |
20 | def test_opposite_user(self):
21 | """ Test if the opposite user is returned """
22 | contact = MessageContact.objects.get(pk=1)
23 | john = User.objects.get(pk=1)
24 | jane = User.objects.get(pk=2)
25 |
26 | # Test the opposites
27 | self.assertEqual(contact.opposite_user(john),
28 | jane)
29 |
30 | self.assertEqual(contact.opposite_user(jane),
31 | john)
32 |
33 | class MessageModelTests(TestCase):
34 | fixtures = ['users', 'messages']
35 |
36 | def test_string_formatting(self):
37 | """ Test the human representation of a message """
38 | message = Message.objects.get(pk=1)
39 | truncated_body = truncate_words(message.body, 10)
40 | self.assertEqual(message.__str__(),
41 | truncated_body)
42 |
43 | class MessageRecipientModelTest(TestCase):
44 | fixtures = ['users', 'messages']
45 |
46 | def test_string_formatting(self):
47 | """ Test the human representation of a recipient """
48 | recipient = MessageRecipient.objects.get(pk=1)
49 |
50 | valid_unicode = '%s' % (recipient.message)
51 |
52 | self.assertEqual(recipient.__str__(),
53 | valid_unicode)
54 |
55 | def test_new(self):
56 | """ Test if the message that is new is correct """
57 | new_message = MessageRecipient.objects.get(pk=1)
58 | read_message = MessageRecipient.objects.get(pk=2)
59 |
60 | self.assertTrue(new_message.is_read())
61 | self.assertFalse(read_message.is_read())
62 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | import sys
3 |
4 | userena = __import__('userena')
5 |
6 | readme_file = 'README.md'
7 | try:
8 | long_description = open(readme_file).read()
9 | except IOError:
10 | sys.stderr.write(
11 | "[ERROR] Cannot find file specified as "
12 | "``long_description`` (%s)\n" % readme_file
13 | )
14 | sys.exit(1)
15 |
16 | install_requires = [
17 | 'easy_thumbnails',
18 | 'django-guardian>=1.4.9',
19 | 'html2text==2014.12.29'
20 | ]
21 |
22 | try:
23 | from collections import OrderedDict
24 | except ImportError:
25 | install_requires.append('ordereddict')
26 |
27 | setup(name='django-userena',
28 | version=userena.get_version(),
29 | description='Complete user management application for Django',
30 | long_description=long_description,
31 | zip_safe=False,
32 | author='Petar Radosevic',
33 | author_email='petar@wunki.org',
34 | url='https://github.com/bread-and-pepper/django-userena/',
35 | download_url='https://github.com/bread-and-pepper/django-userena/downloads',
36 | packages=find_packages(exclude=['demo', 'demo.*']),
37 | include_package_data=True,
38 | install_requires = install_requires,
39 | test_suite='tests.main',
40 | classifiers=[
41 | 'Development Status :: 4 - Beta',
42 | 'Environment :: Web Environment',
43 | 'Framework :: Django',
44 | 'Framework :: Django :: 1.5',
45 | 'Framework :: Django :: 1.6',
46 | 'Framework :: Django :: 1.7',
47 | 'Framework :: Django :: 1.8',
48 | 'Framework :: Django :: 1.9',
49 | 'Intended Audience :: Developers',
50 | 'License :: OSI Approved :: BSD License',
51 | 'Operating System :: OS Independent',
52 | 'Programming Language :: Python',
53 | 'Programming Language :: Python :: 2',
54 | 'Programming Language :: Python :: 2.6',
55 | 'Programming Language :: Python :: 2.7',
56 | 'Programming Language :: Python :: 3',
57 | 'Programming Language :: Python :: 3.2',
58 | 'Programming Language :: Python :: 3.3',
59 | 'Programming Language :: Python :: 3.4',
60 | 'Programming Language :: Python :: 3.5',
61 | 'Topic :: Utilities'
62 | ],
63 | )
64 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/fields.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 | from django.contrib.auth import get_user_model
3 | from django.forms import widgets
4 | from django.utils.translation import ugettext_lazy as _
5 |
6 |
7 | class CommaSeparatedUserInput(widgets.Input):
8 | input_type = 'text'
9 |
10 | def render(self, name, value, attrs=None):
11 | if value is None:
12 | value = ''
13 | elif isinstance(value, (list, tuple)):
14 | value = (', '.join([user.username for user in value]))
15 | return super(CommaSeparatedUserInput, self).render(name, value, attrs)
16 |
17 | class CommaSeparatedUserField(forms.Field):
18 | """
19 | A :class:`CharField` that exists of comma separated usernames.
20 |
21 | :param recipient_filter:
22 | Optional function which receives as :class:`User` as parameter. The
23 | function should return ``True`` if the user is allowed or ``False`` if
24 | the user is not allowed.
25 |
26 | :return:
27 | A list of :class:`User`.
28 |
29 | """
30 | widget = CommaSeparatedUserInput
31 |
32 | def __init__(self, *args, **kwargs):
33 | recipient_filter = kwargs.pop('recipient_filter', None)
34 | self._recipient_filter = recipient_filter
35 | super(CommaSeparatedUserField, self).__init__(*args, **kwargs)
36 |
37 | def clean(self, value):
38 | super(CommaSeparatedUserField, self).clean(value)
39 |
40 | names = set(value.split(','))
41 | names_set = set([name.strip() for name in names])
42 | users = list(get_user_model().objects.filter(username__in=names_set))
43 |
44 | # Check for unknown names.
45 | unknown_names = names_set ^ set([user.username for user in users])
46 |
47 | recipient_filter = self._recipient_filter
48 | invalid_users = []
49 | if recipient_filter is not None:
50 | for r in users:
51 | if recipient_filter(r) is False:
52 | users.remove(r)
53 | invalid_users.append(r.username)
54 |
55 | if unknown_names or invalid_users:
56 | humanized_usernames = ', '.join(list(unknown_names) + invalid_users)
57 | raise forms.ValidationError(_("The following usernames are incorrect: %(users)s.") % {'users': humanized_usernames})
58 |
59 | return users
60 |
--------------------------------------------------------------------------------
/demo/createdb.py:
--------------------------------------------------------------------------------
1 | import MySQLdb
2 | import psycopg2
3 | import os
4 | from wsgi import *
5 |
6 | def create_dbs():
7 | print("create_dbs: let's go.")
8 | django_settings = __import__(os.environ['DJANGO_SETTINGS_MODULE'], fromlist='DATABASES')
9 | print("create_dbs: got settings.")
10 | databases = django_settings.DATABASES
11 | for name, db in databases.iteritems():
12 | host = db['HOST']
13 | user = db['USER']
14 | password = db['PASSWORD']
15 | port = db['PORT']
16 | db_name = db['NAME']
17 | db_type = db['ENGINE']
18 | # see if it is mysql
19 | if db_type.endswith('mysql'):
20 | print 'creating database %s on %s' % (db_name, host)
21 | db = MySQLdb.connect(user=user,
22 | passwd=password,
23 | host=host,
24 | port=port)
25 | cur = db.cursor()
26 | print("Check if database is already there.")
27 | cur.execute("""SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA
28 | WHERE SCHEMA_NAME = %s""", (db_name,))
29 | results = cur.fetchone()
30 | if not results:
31 | print("Database %s doesn't exist, lets create it." % db_name)
32 | sql = """CREATE DATABASE IF NOT EXISTS %s """ % (db_name,)
33 | print("> %s" % sql)
34 | cur.execute(sql)
35 | print(".....")
36 | else:
37 | print("database already exists, moving on to next step.")
38 | # see if it is postgresql
39 | elif db_type.endswith('postgresql_psycopg2'):
40 | print 'creating database %s on %s' % (db_name, host)
41 | con = psycopg2.connect(host=host, user=user, password=password, port=port, database='postgres')
42 | con.set_isolation_level(0)
43 | cur = con.cursor()
44 | try:
45 | cur.execute('CREATE DATABASE %s' % db_name)
46 | except psycopg2.ProgrammingError as detail:
47 | print detail
48 | print 'moving right along...'
49 | else:
50 | print("ERROR: {0} is not supported by this script, you will need to create your database by hand.".format(db_type))
51 |
52 | if __name__ == '__main__':
53 | import sys
54 | print("create_dbs start")
55 | create_dbs()
56 | print("create_dbs all done")
57 |
--------------------------------------------------------------------------------
/userena/compat.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from collections import defaultdict
3 | import django
4 |
5 |
6 | # this default dict will store all compat quirks for parameters of
7 | # django.contrib.auth views
8 | auth_views_compat_quirks = defaultdict(lambda: dict())
9 |
10 | # below are quirks we must use because we can't change some userena API's
11 | # like (url names)
12 | if django.VERSION >= (1, 6, 0): # pragma: no cover
13 | # in django >= 1.6.0 django.contrib.auth.views.reset no longer looks
14 | # for django.contrib.auth.views.password_reset_done but for
15 | # password_reset_done named url. To avoid duplicating urls we
16 | # provide custom post_reset_redirect
17 | auth_views_compat_quirks['userena_password_reset'] = {
18 | 'post_reset_redirect': 'userena_password_reset_done',
19 | }
20 |
21 | # same case as above
22 | auth_views_compat_quirks['userena_password_reset_confirm'] = {
23 | 'post_reset_redirect': 'userena_password_reset_complete',
24 | }
25 | if django.VERSION >= (1, 7, 0):
26 | # Django 1.7 added a new argument to django.contrib.auth.views.password_reset
27 | # called html_email_template_name which allows us to pass it the html version
28 | # of the email
29 | auth_views_compat_quirks['html_email_template_name'] = 'userena/emails/password_reset_message.html'
30 |
31 |
32 | # below are backward compatibility fixes
33 | password_reset_uid_kwarg = 'uidb64'
34 | if django.VERSION < (1, 6, 0): # pragma: no cover
35 | # Django<1.6.0 uses uidb36, we construct urlpattern depending on this
36 | password_reset_uid_kwarg = 'uidb36'
37 |
38 |
39 | # SiteProfileNotAvailable compatibility
40 | if django.VERSION < (1, 7, 0): # pragma: no cover
41 | from django.contrib.auth.models import SiteProfileNotAvailable
42 | else: # pragma: no cover
43 | class SiteProfileNotAvailable(Exception):
44 | pass
45 |
46 |
47 | if django.VERSION < (1, 7, 0):
48 | from django.db.models import get_model
49 | else:
50 | from django.apps import apps
51 | get_model = apps.get_model
52 |
53 |
54 | # optparse/argparse compatibility helper for simple cases (long options only)
55 | # for an example useage see userena/management/commands/check_permissions.py
56 | if django.VERSION < (1, 8):
57 | from optparse import make_option
58 | def make_options(options):
59 | return list(make_option(opt, **attrs) for opt, attrs in options)
60 | else:
61 | def make_options(options):
62 | return ()
63 |
--------------------------------------------------------------------------------
/userena/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 2.0.6 on 2018-06-13 11:39
2 |
3 | from django.conf import settings
4 | from django.db import migrations, models
5 | import django.db.models.deletion
6 | import userena.managers
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | initial = True
12 |
13 | dependencies = [
14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
15 | ]
16 |
17 | operations = [
18 | migrations.CreateModel(
19 | name='UserenaSignup',
20 | fields=[
21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22 | ('last_active', models.DateTimeField(blank=True, help_text='The last date that the user was active.', null=True, verbose_name='last active')),
23 | ('activation_key', models.CharField(blank=True, max_length=40, verbose_name='activation key')),
24 | ('invitation_key', models.CharField(blank=True, max_length=40, verbose_name='invitation key')),
25 | ('activation_notification_send', models.BooleanField(default=False, help_text='Designates whether this user has already got a notification about activating their account.', verbose_name='notification send')),
26 | ('email_unconfirmed', models.EmailField(blank=True, help_text='Temporary email address when the user requests an email change.', max_length=254, verbose_name='unconfirmed email address')),
27 | ('email_confirmation_key', models.CharField(blank=True, max_length=40, verbose_name='unconfirmed email verification key')),
28 | ('email_confirmation_key_created', models.DateTimeField(blank=True, null=True, verbose_name='creation date of email confirmation key')),
29 | ('invitation_status', models.CharField(choices=[('INV', 'Invitation Mail was sent'), ('PSWRST', 'Password was reset by user'), ('PRFEDIT', 'Profile was edited by user')], default='INV', max_length=7)),
30 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='userena_signup', to=settings.AUTH_USER_MODEL, verbose_name='user')),
31 | ],
32 | options={
33 | 'verbose_name': 'userena registration',
34 | 'verbose_name_plural': 'userena registrations',
35 | },
36 | managers=[
37 | ('objects', userena.managers.UserenaManager()),
38 | ],
39 | ),
40 | ]
41 |
--------------------------------------------------------------------------------
/userena/fixtures/users.json:
--------------------------------------------------------------------------------
1 | [{"pk": 1,
2 | "model": "auth.user",
3 | "fields": {"username": "john",
4 | "first_name": "John",
5 | "last_name": "Doe",
6 | "is_active": true,
7 | "is_superuser": true,
8 | "is_staff": true,
9 | "last_login": "2010-08-18T03:07:03.019Z",
10 | "groups": [],
11 | "user_permissions": [],
12 | "password": "sha1$645fd$8d18b38b96f72363bc9438bc1d340081b87e0fbe",
13 | "email": "john@example.com",
14 | "date_joined": "2010-08-17T10:31:55.019Z"}},
15 | {"pk": 2,
16 | "model": "auth.user",
17 | "fields": {"username": "jane",
18 | "first_name": "Jane",
19 | "last_name": "Doe",
20 | "is_active": true,
21 | "is_superuser": false,
22 | "is_staff": false,
23 | "last_login": "2010-08-18T03:15:19.019Z",
24 | "groups": [],
25 | "user_permissions": [],
26 | "password": "sha1$645fd$8d18b38b96f72363bc9438bc1d340081b87e0fbe",
27 | "email": "jane@example.com",
28 | "date_joined": "2010-08-18T03:13:57.019Z"}},
29 | {"pk": 3,
30 | "model": "auth.user",
31 | "fields": {"username": "arie",
32 | "first_name": "Arie",
33 | "last_name": "de Beuker",
34 | "is_active": true,
35 | "is_superuser": false,
36 | "is_staff": false,
37 | "last_login": "2010-08-18T00:00:00.019Z",
38 | "groups": [],
39 | "user_permissions": [],
40 | "password": "sha1$645fd$8d18b38b96f72363bc9438bc1d340081b87e0fbe",
41 | "email": "arie@example.com",
42 | "date_joined": "2010-01-01T00:00:00.019Z"}},
43 |
44 | {"pk": 1,
45 | "model": "userena.userenasignup",
46 | "fields": {"user": 1,
47 | "activation_key": "ALREADY_ACTIVATED",
48 | "last_active": "2010-08-17T13:37:03.019Z",
49 | "activation_notification_send": false}},
50 | {"pk": 2,
51 | "model": "userena.userenasignup",
52 | "fields": {"user": 2,
53 | "activation_key": "ALREADY_ACTIVATED",
54 | "last_active": null,
55 | "activation_notification_send": false}},
56 | {"pk": 3,
57 | "model": "userena.userenasignup",
58 | "fields": {"user": 3,
59 | "activation_key": "ALREADY_ACTIVATED",
60 | "last_active": null,
61 | "activation_notification_send": false}}
62 | ]
63 |
--------------------------------------------------------------------------------
/userena/templates/userena/profile_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'userena/base_userena.html' %}
2 | {% load i18n %}
3 |
4 | {% block title %}{% blocktrans with profile.user.username as username %}{{ username }}'s profile.{% endblocktrans %}{% endblock %}
5 | {% block content_title %}{{ profile.user.username }} {% if profile.user.get_full_name %}({{ profile.user.get_full_name }}){% endif %} {% endblock %}
6 |
7 | {% block content %}
8 |
9 | {% block profile_navigation %}
10 | {# Dirty hack. Will use django-guardian in the future. #}
11 | {% if user.username == profile.user.username %}
12 |
20 | {% endif %}
21 | {% endblock %}
22 |
23 |
24 | {% block profile_details %}
25 |
26 |
27 | {% block profile_definition_list %}
28 | {% if profile.user.get_full_name %}
29 | {% trans "Name" %}
30 | {{ profile.user.get_full_name }}
31 | {% endif %}
32 | {% if profile.user.email and not hide_email %}
33 | {% trans "Email" %}
34 | {{ profile.user.email }}
35 | {% endif %}
36 | {% if profile.age %}
37 | {% trans "Age" %}
38 | {{ profile.age }}
39 | {% endif %}
40 | {% if profile.website %}
41 | {% trans "Website" %}
42 | {{ profile.website|urlize }}
43 | {% endif %}
44 | {% if profile.location %}
45 | {% trans "Location" %}
46 | {{ profile.location }}
47 | {% endif %}
48 | {% if profile.about_me %}
49 | {% trans "About me" %}
50 | {{ profile.about_me }}
51 | {% endif %}
52 | {% endblock %}
53 |
54 | {% endblock %}
55 |
56 |
57 | {% endblock %}
58 |
--------------------------------------------------------------------------------
/demo/locale/nl/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2010-10-27 10:05-0500\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Language: \n"
19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20 |
21 | #: settings.py:36
22 | msgid "English"
23 | msgstr "Engels"
24 |
25 | #: settings.py:37
26 | msgid "Dutch"
27 | msgstr "Nederlands"
28 |
29 | #: profiles/models.py:11
30 | msgid "Male"
31 | msgstr ""
32 |
33 | #: profiles/models.py:12
34 | msgid "Female"
35 | msgstr ""
36 |
37 | #: profiles/models.py:14
38 | msgid "gender"
39 | msgstr ""
40 |
41 | #: profiles/models.py:18
42 | msgid "website"
43 | msgstr ""
44 |
45 | #: profiles/models.py:19
46 | msgid "location"
47 | msgstr ""
48 |
49 | #: profiles/models.py:20
50 | msgid "birth date"
51 | msgstr ""
52 |
53 | #: profiles/models.py:21
54 | msgid "about me"
55 | msgstr ""
56 |
57 | #: templates/404.html:4
58 | msgid "Page not found"
59 | msgstr ""
60 |
61 | #: templates/404.html:7
62 | msgid "Page could not be found."
63 | msgstr ""
64 |
65 | #: templates/404.html:8
66 | msgid ""
67 | "If you think this is a bug in userena, please create an issue at Github."
68 | msgstr ""
69 |
70 | #: templates/base.html:32
71 | #, fuzzy
72 | msgid "Choose language"
73 | msgstr "Verander taal"
74 |
75 | #: templates/base.html:39
76 | msgid "Change language"
77 | msgstr "Verander taal"
78 |
79 | #: templates/base.html:51
80 | msgid "Home"
81 | msgstr "Thuis"
82 |
83 | #: templates/base.html:53
84 | msgid "Profiles"
85 | msgstr ""
86 |
87 | #: templates/base.html:54
88 | #, fuzzy
89 | msgid "Account settings"
90 | msgstr "Accounts"
91 |
92 | #: templates/base.html:55
93 | msgid "Signout"
94 | msgstr "Uitloggen"
95 |
96 | #: templates/base.html:57
97 | msgid "Signin"
98 | msgstr "Inloggen"
99 |
100 | #: templates/base.html:58
101 | msgid "Signup"
102 | msgstr "Inschrijven"
103 |
104 | #: templates/base.html:67
105 | msgid "Fork me hard"
106 | msgstr "Fork me!"
107 |
108 | #: templates/base.html:74
109 | msgid "Made by"
110 | msgstr "Gemaakt door"
111 |
112 | #~ msgid "Your account"
113 | #~ msgstr "Jouw account"
114 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/tests/test_fields.py:
--------------------------------------------------------------------------------
1 | #encoding:utf-8
2 | from __future__ import unicode_literals
3 |
4 | import re
5 |
6 | from django.test import TestCase
7 | from django import forms
8 |
9 | from userena.contrib.umessages.fields import CommaSeparatedUserField
10 |
11 | class CommaSeparatedTestForm(forms.Form):
12 | users = CommaSeparatedUserField()
13 |
14 | def __init__(self, *args, **kwargs):
15 | super(CommaSeparatedTestForm, self).__init__(*args, **kwargs)
16 | self.fields['users']._recipient_filter = self.filter_jane
17 |
18 | def filter_jane(self, user):
19 | if user.username == 'jane':
20 | return False
21 | return True
22 |
23 | class CommaSeperatedFieldTests(TestCase):
24 | fixtures = ['users',]
25 |
26 | def test_invalid_data(self):
27 | # Test invalid data supplied to the field.
28 | invalid_data_dicts = [
29 | # Empty username
30 | {'data': {'users': ''},
31 | 'error': ('users', ['This field is required.'])},
32 | # No data
33 | {'data': {},
34 | 'error': ('users', ['This field is required.'])},
35 | # A list
36 | {'data': {'users': []},
37 | 'error': ('users', ['This field is required.'])},
38 | # Forbidden username
39 | {'data': {'users': 'jane'},
40 | 'error': ('users', ['The following usernames are incorrect: jane.'])},
41 | # Non-existant username
42 | {'data': {'users': 'foo'},
43 | 'error': ('users', ['The following usernames are incorrect: foo.'])},
44 | # Multiple invalid usernames
45 | {'data': {'users': 'foo, bar'},
46 | 'error': ('users', ['The following usernames are incorrect: (foo|bar), (foo|bar).'])},
47 | # Valid and invalid
48 | {'data': {'users': 'foo, john, bar'},
49 | 'error': ('users', ['The following usernames are incorrect: (foo|bar), (foo|bar).'])},
50 | # Extra whitespace
51 | {'data': {'users': 'foo, john '},
52 | 'error': ('users', ['The following usernames are incorrect: foo.'])},
53 |
54 | ]
55 | for invalid_dict in invalid_data_dicts:
56 | form = CommaSeparatedTestForm(data=invalid_dict['data'])
57 | self.assertFalse(form.is_valid())
58 | # self.assertEqual(form.errors[invalid_dict['error'][0]],
59 | # invalid_dict['error'][1])
60 | self.assertTrue(re.match(
61 | invalid_dict['error'][1][0],
62 | form.errors[invalid_dict['error'][0]][0]
63 | ))
64 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. Userena documentation master file, created by
2 | sphinx-quickstart on Fri Jul 2 09:28:08 2010.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Userena Introduction
7 | ====================
8 |
9 | This documentation covers |release| release of django-userena application.
10 | A Django application that takes care of your account needs.
11 |
12 | Why userena?
13 | ================
14 |
15 | Because we have done the hard work for you. Userena supplies you with signup,
16 | signin, account editing, privacy settings and private messaging. All you have
17 | to do is plug it into your project and you will have created account
18 | management with the following options:
19 |
20 | - User has to **activate** their account by clicking on a activation link
21 | in an email sent to them.
22 |
23 | - **Permissions** for viewing, changing and deleting accounts is
24 | implemented on an user and object basis with the help of django-guardian.
25 |
26 | - Optionally **secure** userena by using https. If you change the settings
27 | to use https, userena will switch to the secure protocol on it's views
28 | and emails.
29 |
30 | - All **templates** are already supplied for you. Only override those that
31 | don't fit with your needs.
32 |
33 | - Mugshots are supplied by **Gravatar** or uploaded by the user. The
34 | default mugshot can be set in the settings.
35 |
36 | - **Messaging** system between users that either get's displayed as
37 | conversations (iPhone like) or sorted per subject (Gmail).
38 |
39 | Help out
40 | ========
41 |
42 | Found a bug in userena? File an issue at Github. Have an improvement? Fork it
43 | and add it, or if you can't code it, contact us to do it.
44 |
45 |
46 | Deprecation warnigns
47 | ====================
48 |
49 | 2.0.0 version:
50 |
51 | - ``userena.utils.get_user_model()`` is deprecated and will be removed in
52 | version 3.0.0. Use ``django.contrib.auth.get_user_model()``
53 |
54 |
55 | Changes and releases
56 | ====================
57 |
58 | For changes history and available releases see following pages on GitHub
59 | repository:
60 |
61 | * `UDATES.md `_
62 | * `releases `_
63 |
64 |
65 | Contents
66 | ========
67 |
68 | .. toctree::
69 | :maxdepth: 2
70 |
71 | installation
72 | settings
73 | signals
74 | commands
75 | faq
76 | api/index
77 |
78 | Contrib: uMessages
79 | ~~~~~~~~~~~~~~~~~~
80 |
81 | .. toctree::
82 | :maxdepth: 3
83 |
84 | contrib/umessages/index
85 |
86 | Indices and tables
87 | ==================
88 |
89 | * :ref:`genindex`
90 | * :ref:`search`
91 |
--------------------------------------------------------------------------------
/userena/tests/test_backends.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 | from django.contrib.auth import get_user_model
3 |
4 | from userena.backends import UserenaAuthenticationBackend
5 |
6 | User = get_user_model()
7 |
8 |
9 | class UserenaAuthenticationBackendTests(TestCase):
10 | """
11 | Test the ``UserenaAuthenticationBackend`` which should return a ``User``
12 | when supplied with a username/email and a correct password.
13 |
14 | """
15 | fixtures = ['users',]
16 | backend = UserenaAuthenticationBackend()
17 |
18 | def test_with_username(self):
19 | """ Test the backend when usernames are supplied. """
20 | # Invalid usernames or passwords
21 | invalid_data_dicts = [
22 | # Invalid password
23 | {'identification': 'john',
24 | 'password': 'inhalefish'},
25 | # Invalid username
26 | {'identification': 'alice',
27 | 'password': 'blowfish'},
28 | ]
29 | for invalid_dict in invalid_data_dicts:
30 | result = self.backend.authenticate(identification=invalid_dict['identification'],
31 | password=invalid_dict['password'])
32 | self.assertFalse(isinstance(result, User))
33 |
34 | # Valid username and password
35 | result = self.backend.authenticate(identification='john',
36 | password='blowfish')
37 | self.assertTrue(isinstance(result, User))
38 |
39 | def test_with_email(self):
40 | """ Test the backend when email address is supplied """
41 | # Invalid e-mail adressses or passwords
42 | invalid_data_dicts = [
43 | # Invalid password
44 | {'identification': 'john@example.com',
45 | 'password': 'inhalefish'},
46 | # Invalid e-mail address
47 | {'identification': 'alice@example.com',
48 | 'password': 'blowfish'},
49 | ]
50 | for invalid_dict in invalid_data_dicts:
51 | result = self.backend.authenticate(identification=invalid_dict['identification'],
52 | password=invalid_dict['password'])
53 | self.assertFalse(isinstance(result, User))
54 |
55 | # Valid e-email address and password
56 | result = self.backend.authenticate(identification='john@example.com',
57 | password='blowfish')
58 | self.assertTrue(isinstance(result, User))
59 |
60 | def test_get_user(self):
61 | """ Test that the user is returned """
62 | user = self.backend.get_user(1)
63 | self.assertEqual(user.username, 'john')
64 |
65 | # None should be returned when false id.
66 | user = self.backend.get_user(99)
67 | self.assertFalse(user)
68 |
--------------------------------------------------------------------------------
/demo/demo/settings_dotcloud.py:
--------------------------------------------------------------------------------
1 | from settings import *
2 |
3 | import json
4 |
5 | DEBUG = False
6 | TEMPLATE_DEBUG = DEBUG
7 | with open('/home/dotcloud/environment.json') as f:
8 | env = json.load(f)
9 | DATABASES = {
10 | 'default': {
11 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
12 | 'NAME': 'django_userena',
13 | 'USER': env['DOTCLOUD_DB_SQL_LOGIN'],
14 | 'PASSWORD': env['DOTCLOUD_DB_SQL_PASSWORD'],
15 | 'HOST': env['DOTCLOUD_DB_SQL_HOST'],
16 | 'PORT': int(env['DOTCLOUD_DB_SQL_PORT']),
17 | }
18 | }
19 |
20 | # Email settings
21 | EMAIL_HOST = 'smtp.gmail.com'
22 | EMAIL_HOST_USER = env['GMAIL_USER']
23 | EMAIL_HOST_PASSWORD = env['GMAIL_PASSWORD']
24 | EMAIL_PORT = 587
25 | EMAIL_USE_TLS = True
26 | DEFAULT_FROM_EMAIL = "Userena "
27 |
28 | # Media and static
29 | MEDIA_ROOT = '/home/dotcloud/data/media/'
30 | STATIC_ROOT = '/home/dotcloud/volatile/static/'
31 |
32 | # Logging
33 | LOGGING = {
34 | 'version': 1,
35 | 'disable_existing_loggers': True,
36 | 'formatters': {
37 | 'verbose': {
38 | 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
39 | },
40 | 'simple': {
41 | 'format': '%(levelname)s %(message)s'
42 | },
43 | },
44 | 'handlers': {
45 | 'null': {
46 | 'level':'DEBUG',
47 | 'class':'django.utils.log.NullHandler',
48 | },
49 | 'console': {
50 | 'level': 'DEBUG',
51 | 'class': 'logging.StreamHandler',
52 | 'formatter': 'verbose'
53 | },
54 | 'log_file': {
55 | 'level': 'DEBUG',
56 | 'class': 'logging.handlers.RotatingFileHandler',
57 | 'formatter': 'verbose',
58 | 'filename': '/var/log/supervisor/userena.log',
59 | 'maxBytes': 1024*1024*25, # 25 MB
60 | 'backupCount': 5,
61 | },
62 | 'mail_admins': {
63 | 'level': 'ERROR',
64 | 'class': 'django.utils.log.AdminEmailHandler'
65 | }
66 | },
67 | 'loggers': {
68 | 'django': {
69 | 'handlers': ['console', 'log_file', 'mail_admins'],
70 | 'level': 'INFO',
71 | 'propagate': True,
72 | },
73 | 'django.request': {
74 | 'handlers': ['console', 'log_file', 'mail_admins'],
75 | 'level': 'ERROR',
76 | 'propagate': False,
77 | },
78 | 'django.db.backends': {
79 | 'handlers': ['console', 'log_file', 'mail_admins'],
80 | 'level': 'INFO',
81 | 'propagate': False,
82 | },
83 | # Catch All Logger -- Captures any other logging
84 | '': {
85 | 'handlers': ['console', 'log_file', 'mail_admins'],
86 | 'level': 'INFO',
87 | 'propagate': True,
88 | }
89 | }
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/templatetags/umessages_tags.py:
--------------------------------------------------------------------------------
1 | from django import template
2 |
3 | from userena.contrib.umessages.models import MessageRecipient
4 |
5 | import re
6 |
7 | register = template.Library()
8 |
9 | class MessageCount(template.Node):
10 | def __init__(self, um_from_user, var_name, um_to_user=None):
11 | self.user = template.Variable(um_from_user)
12 | self.var_name = var_name
13 | if um_to_user:
14 | self.um_to_user = template.Variable(um_to_user)
15 | else: self.um_to_user = um_to_user
16 |
17 | def render(self, context):
18 | try:
19 | user = self.user.resolve(context)
20 | except template.VariableDoesNotExist:
21 | return ''
22 |
23 | if not self.um_to_user:
24 | message_count = MessageRecipient.objects.count_unread_messages_for(user)
25 |
26 | else:
27 | try:
28 | um_to_user = self.um_to_user.resolve(context)
29 | except template.VariableDoesNotExist:
30 | return ''
31 |
32 | message_count = MessageRecipient.objects.count_unread_messages_between(user,
33 | um_to_user)
34 |
35 | context[self.var_name] = message_count
36 |
37 | return ''
38 |
39 | @register.tag
40 | def get_unread_message_count_for(parser, token):
41 | """
42 | Returns the unread message count for a user.
43 |
44 | Syntax::
45 |
46 | {% get_unread_message_count_for [user] as [var_name] %}
47 |
48 | Example usage::
49 |
50 | {% get_unread_message_count_for pero as message_count %}
51 |
52 | """
53 | try:
54 | tag_name, arg = token.contents.split(None, 1)
55 | except ValueError:
56 | raise template.TemplateSyntaxError("%s tag requires arguments" % token.contents.split()[0])
57 | m = re.search(r'(.*?) as (\w+)', arg)
58 | if not m:
59 | raise template.TemplateSyntaxError("%s tag had invalid arguments" % tag_name)
60 | user, var_name = m.groups()
61 | return MessageCount(user, var_name)
62 |
63 | @register.tag
64 | def get_unread_message_count_between(parser, token):
65 | """
66 | Returns the unread message count between two users.
67 |
68 | Syntax::
69 |
70 | {% get_unread_message_count_between [user] and [user] as [var_name] %}
71 |
72 | Example usage::
73 |
74 | {% get_unread_message_count_between funky and wunki as message_count %}
75 |
76 | """
77 | try:
78 | tag_name, arg = token.contents.split(None, 1)
79 | except ValueError:
80 | raise template.TemplateSyntaxError("%s tag requires arguments" % token.contents.split()[0])
81 | m = re.search(r'(.*?) and (.*?) as (\w+)', arg)
82 | if not m:
83 | raise template.TemplateSyntaxError("%s tag had invalid arguments" % tag_name)
84 | um_from_user, um_to_user, var_name = m.groups()
85 | return MessageCount(um_from_user, var_name, um_to_user)
86 |
--------------------------------------------------------------------------------
/userena/runtests/runtests.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | import os
4 | import sys
5 |
6 | # fix sys path so we don't need to setup PYTHONPATH
7 | sys.path.append(os.path.join(os.path.dirname(__file__), "..", ".."))
8 | os.environ['DJANGO_SETTINGS_MODULE'] = 'userena.runtests.settings'
9 |
10 | import django
11 |
12 | if django.VERSION >= (1, 7, 0):
13 | # starting from 1.7.0 we need to run setup() in order to populate
14 | # app config
15 | django.setup()
16 |
17 | from django.conf import settings
18 | from django.test.utils import get_runner
19 |
20 |
21 | def usage():
22 | return """
23 | Usage: python runtests.py [app] [-p ]
24 |
25 | """
26 |
27 |
28 | def main():
29 | TestRunner = get_runner(settings)
30 |
31 | # Ugly parameter parsing. We probably want to improve that in future
32 | # or just use default django test command. This may be problematic,
33 | # knowing how testing in Django changes from version to version.
34 | if '-p' in sys.argv:
35 | try:
36 | pos = sys.argv.index('-p')
37 | pattern = sys.argv.pop(pos) and sys.argv.pop(pos)
38 | except IndexError:
39 | print(usage())
40 | sys.exit(1)
41 | else:
42 | pattern = None
43 |
44 | test_modules = sys.argv[1:]
45 |
46 | test_runner = TestRunner(verbosity=2, failfast=False, pattern=pattern)
47 |
48 | if len(sys.argv) > 1:
49 | test_modules = sys.argv[1:]
50 | elif len(sys.argv) == 1:
51 | test_modules = []
52 | else:
53 | print(usage())
54 | sys.exit(1)
55 |
56 | if (1, 6, 0) <= django.VERSION < (1, 9, 0):
57 | # this is a compat hack because in django>=1.6.0 you must provide
58 | # module like "userena.contrib.umessages" not "umessages"
59 | from django.db.models import get_app
60 | test_modules = [
61 | # be more strict by adding .tests to not run umessages tests twice
62 | # if both userena and umessages are tested
63 | get_app(module_name).__name__[:-7] + ".tests"
64 | for module_name
65 | in test_modules
66 | ]
67 | elif django.VERSION >= (1, 9, 0):
68 | from django.apps import apps
69 | test_modules = [
70 | # be more strict by adding .tests to not run umessages tests twice
71 | # if both userena and umessages are tested
72 | apps.get_app_config(module_name).name + ".tests"
73 | for module_name
74 | in test_modules
75 | ]
76 |
77 | if django.VERSION < (1, 7, 0):
78 | # starting from 1.7.0 built in django migrations are run
79 | # for older releases this patch is required to enable testing with
80 | # migrations
81 | from south.management.commands import patch_for_test_db_setup
82 | patch_for_test_db_setup()
83 |
84 | failures = test_runner.run_tests(test_modules or ['userena'])
85 | sys.exit(failures)
86 |
87 |
88 | if __name__ == '__main__':
89 | main()
90 |
--------------------------------------------------------------------------------
/userena/mail.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import re
3 |
4 | from django.conf import settings
5 | from django.template.loader import render_to_string
6 | from django.utils.translation import ugettext as _
7 | from django.core.mail import EmailMultiAlternatives
8 |
9 | from userena import settings as userena_settings
10 |
11 | from html2text import html2text
12 |
13 | def send_mail(subject, message_plain, message_html, email_from, email_to,
14 | custom_headers={}, attachments=()):
15 | """
16 | Build the email as a multipart message containing
17 | a multipart alternative for text (plain, HTML) plus
18 | all the attached files.
19 | """
20 | if not message_plain and not message_html:
21 | raise ValueError(_("Either message_plain or message_html should be not None"))
22 |
23 | if not message_plain:
24 | message_plain = html2text(message_html)
25 |
26 | message = {}
27 |
28 | message['subject'] = subject
29 | message['body'] = message_plain
30 | message['from_email'] = email_from
31 | message['to'] = email_to
32 | if attachments:
33 | message['attachments'] = attachments
34 | if custom_headers:
35 | message['headers'] = custom_headers
36 |
37 | msg = EmailMultiAlternatives(**message)
38 | if message_html:
39 | msg.attach_alternative(message_html, "text/html")
40 | msg.send()
41 |
42 |
43 | def wrap_attachment():
44 | pass
45 |
46 |
47 | class UserenaConfirmationMail(object):
48 |
49 | _message_txt = 'userena/emails/{0}_email_message{1}.txt'
50 | _message_html = 'userena/emails/{0}_email_message{1}.html'
51 | _subject_txt = 'userena/emails/{0}_email_subject{1}.txt'
52 |
53 | def __init__(self, context):
54 | self.context = context
55 |
56 | def generate_mail(self, type_mail, version=""):
57 | self.type_mail = type_mail
58 | self.message_txt = self._message_txt.format(type_mail, version)
59 | self.message_html = self._message_html.format(type_mail, version)
60 | self.subject_txt = self._subject_txt.format(type_mail, version)
61 | self.subject = self._subject()
62 | self.message_html = self._message_in_html()
63 | self.message = self._message_in_txt()
64 |
65 | def send_mail(self, email):
66 | send_mail(self.subject, self.message,
67 | self.message_html, settings.DEFAULT_FROM_EMAIL,
68 | [email])
69 |
70 | def _message_in_html(self):
71 | if userena_settings.USERENA_HTML_EMAIL:
72 | return render_to_string(self.message_html, self.context)
73 | return None
74 |
75 | def _message_in_txt(self):
76 | if (not userena_settings.USERENA_HTML_EMAIL
77 | or not self.message_html
78 | or userena_settings.USERENA_USE_PLAIN_TEMPLATE):
79 | return render_to_string(self.message_txt, self.context)
80 | return None
81 |
82 | def _subject(self):
83 | subject = render_to_string(self.subject_txt, self.context)
84 | subject = ''.join(subject.splitlines())
85 | return subject
86 |
--------------------------------------------------------------------------------
/userena/tests/tests_middleware.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth import get_user_model
2 | from django.http import HttpRequest
3 | from django.test import TestCase
4 |
5 | from userena.tests.profiles.models import Profile
6 | from userena.middleware import UserenaLocaleMiddleware
7 | from userena import settings as userena_settings
8 | from userena.utils import get_user_profile, get_profile_model
9 |
10 | User = get_user_model()
11 |
12 |
13 | def has_profile(user):
14 | """Test utility function to check if user has profile"""
15 | profile_model = get_profile_model()
16 | try:
17 | profile = user.get_profile()
18 | except AttributeError:
19 | related_name = profile_model._meta.get_field('user')\
20 | .related_query_name()
21 | profile = getattr(user, related_name, None)
22 | except profile_model.DoesNotExist:
23 | profile = None
24 |
25 | return bool(profile)
26 |
27 |
28 |
29 | class UserenaLocaleMiddlewareTests(TestCase):
30 | """ Test the ``UserenaLocaleMiddleware`` """
31 | fixtures = ['users', 'profiles']
32 |
33 | def _get_request_with_user(self, user):
34 | """ Fake a request with an user """
35 | request = HttpRequest()
36 | request.META = {
37 | 'SERVER_NAME': 'testserver',
38 | 'SERVER_PORT': 80,
39 | }
40 | request.method = 'GET'
41 | request.session = {}
42 |
43 | # Add user
44 | request.user = user
45 | return request
46 |
47 | def test_preference_user(self):
48 | """ Test the language preference of two users """
49 | users = ((1, 'nl'),
50 | (2, 'en'))
51 |
52 | for pk, lang in users:
53 | user = User.objects.get(pk=pk)
54 | profile = get_user_profile(user=user)
55 |
56 | req = self._get_request_with_user(user)
57 |
58 | # Check that the user has this preference
59 | self.assertEqual(profile.language, lang)
60 |
61 | # Request should have a ``LANGUAGE_CODE`` with dutch
62 | UserenaLocaleMiddleware().process_request(req)
63 | self.assertEqual(req.LANGUAGE_CODE, lang)
64 |
65 | def test_without_profile(self):
66 | """ Middleware should do nothing when a user has no profile """
67 | # Delete the profile
68 | Profile.objects.get(pk=1).delete()
69 | user = User.objects.get(pk=1)
70 |
71 | # User shouldn't have a profile
72 | self.assertFalse(has_profile(user))
73 |
74 | req = self._get_request_with_user(user)
75 | UserenaLocaleMiddleware().process_request(req)
76 |
77 | self.assertFalse(hasattr(req, 'LANGUAGE_CODE'))
78 |
79 | def test_without_language_field(self):
80 | """ Middleware should do nothing if the profile has no language field """
81 | userena_settings.USERENA_LANGUAGE_FIELD = 'non_existant_language_field'
82 | user = User.objects.get(pk=1)
83 |
84 | req = self._get_request_with_user(user)
85 |
86 | # Middleware should do nothing
87 | UserenaLocaleMiddleware().process_request(req)
88 | self.assertFalse(hasattr(req, 'LANGUAGE_CODE'))
89 |
--------------------------------------------------------------------------------
/userena/tests/test_privacy.py:
--------------------------------------------------------------------------------
1 | from django.urls import reverse
2 | from django.test import TestCase
3 |
4 | from userena.tests.profiles.models import Profile
5 |
6 | class PrivacyTests(TestCase):
7 | """
8 | Privacy testing of views concerning profiles.
9 |
10 | Test the privacy of the views that are available with three type of users:
11 |
12 | - Anonymous: An user that is not signed in.
13 | - Registered: An user that is registered and signed in.
14 | - Superuser: An user that is administrator at the site.
15 |
16 | """
17 | fixtures = ['users', 'profiles']
18 |
19 | reg_user = {'username': 'jane',
20 | 'password': 'blowfish'}
21 |
22 | super_user = {'username': 'john',
23 | 'password': 'blowfish'}
24 |
25 | detail_profile_url = reverse('userena_profile_detail',
26 | kwargs={'username': 'john'})
27 |
28 | edit_profile_url = reverse('userena_profile_edit',
29 | kwargs={'username': 'john'})
30 |
31 | def _test_status_codes(self, url, users_status):
32 | """
33 | Test if the status codes are corresponding to what that user should
34 | see.
35 |
36 | """
37 | for user, status in users_status:
38 | if user:
39 | self.client.login(**user)
40 | response = self.client.get(url, follow=True)
41 | self.assertEqual(response.status_code, status)
42 |
43 | def test_detail_open_profile_view(self):
44 | """ Viewing an open profile should be visible to everyone """
45 | profile = Profile.objects.get(pk=1)
46 | profile.privacy = 'open'
47 | profile.save()
48 |
49 | users_status = (
50 | (None, 200),
51 | (self.reg_user, 200),
52 | (self.super_user, 200)
53 | )
54 | self._test_status_codes(self.detail_profile_url, users_status)
55 |
56 | def test_detail_registered_profile_view(self):
57 | """ Viewing a users who's privacy is registered """
58 | profile = Profile.objects.get(pk=1)
59 | profile.privacy = 'registered'
60 | profile.save()
61 |
62 | users_status = (
63 | (None, 403),
64 | (self.reg_user, 200),
65 | (self.super_user, 200)
66 | )
67 | self._test_status_codes(self.detail_profile_url, users_status)
68 |
69 | def test_detail_closed_profile_view(self):
70 | """ Viewing a closed profile should only by visible to the admin """
71 | profile = Profile.objects.get(pk=1)
72 | profile.privacy = 'closed'
73 | profile.save()
74 |
75 | users_status = (
76 | (None, 403),
77 | (self.reg_user, 403),
78 | (self.super_user, 200)
79 | )
80 | self._test_status_codes(self.detail_profile_url, users_status)
81 |
82 | def test_edit_profile_view(self):
83 | """ Editing a profile should only be available to the owner and the admin """
84 | profile = Profile.objects.get(pk=1)
85 |
86 | users_status = (
87 | (None, 403),
88 | (self.reg_user, 403),
89 | (self.super_user, 200)
90 | )
91 | self._test_status_codes(self.edit_profile_url, users_status)
92 |
--------------------------------------------------------------------------------
/demo/demo/templates/static/promo.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% block content %}
3 |
4 |
django-userena Accounts made beautifully simple
5 |
6 |
Userena is a Django application that supplies your Django project with
7 | full account management. It's a fully customizable application that takes
8 | care of the signup, activation, messaging and more. It's BSD licensed, which
9 | means you can use it commercially for free!
10 |
11 |
Got any feedback? Email me at petar@breadandpepper.com. For bugs you can report an issue at Github .
12 |
13 |
14 |
15 |
16 | What is userena?
17 |
18 | Userena is an django application that supplies your project with full
19 | account management. It handles the signup, activation, signin etc. for you.
20 | It also supplies your users with an optional profile model that integrates
21 | gravatar avatars. Signup at
22 | this website and try it out for yourself.
23 |
24 |
25 |
26 | It's free!
27 |
28 | Userena is licensed under the BSD license. This means it's free for
29 | commercial use. You don't have to worry about any licensing..
30 |
31 |
32 | Used in production
33 |
34 | Bread & Pepper uses userena
35 | in their own projects. Because of this we have often revised the code and
36 | spared you repeating this work. This has made us confident enough to be able
37 | to say that the the software is ready for production. Because it's open
38 | source, we also get feedback from the community that we use to build a solid
39 | product.
40 |
41 |
42 | 100% test coverage
43 |
44 | Account management is an important aspect of any website, therefore we have
45 | chosen for 100% test coverage for every bit of code. This reduces bugs and
46 | makes sure that we don't introduce any new ones when updating.
47 |
48 |
49 | Messaging
50 |
51 | Userena comes with an optional, chat-like, messaging system. This allows your
52 | users to communicate with each other through your site. It's use is intuitive
53 | and easy!
54 |
55 |
56 | Privacy
57 |
58 | Permission for viewing other peoples profiles are managed by
59 | django-guardian .
60 | This way we can let the user decide if they want to share their profile to
61 | everyone, registered users or nobody.
62 |
63 |
64 |
65 |
79 | {% endblock %}
80 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 |
15 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
16 |
17 | help:
18 | @echo "Please use \`make ' where is one of"
19 | @echo " html to make standalone HTML files"
20 | @echo " dirhtml to make HTML files named index.html in directories"
21 | @echo " pickle to make pickle files"
22 | @echo " json to make JSON files"
23 | @echo " htmlhelp to make HTML files and a HTML help project"
24 | @echo " qthelp to make HTML files and a qthelp project"
25 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
26 | @echo " changes to make an overview of all changed/added/deprecated items"
27 | @echo " linkcheck to check all external links for integrity"
28 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
29 |
30 | clean:
31 | -rm -rf $(BUILDDIR)/*
32 |
33 | html:
34 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
35 | @echo
36 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
37 |
38 | dirhtml:
39 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
40 | @echo
41 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
42 |
43 | pickle:
44 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
45 | @echo
46 | @echo "Build finished; now you can process the pickle files."
47 |
48 | json:
49 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
50 | @echo
51 | @echo "Build finished; now you can process the JSON files."
52 |
53 | htmlhelp:
54 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
55 | @echo
56 | @echo "Build finished; now you can run HTML Help Workshop with the" \
57 | ".hhp project file in $(BUILDDIR)/htmlhelp."
58 |
59 | qthelp:
60 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
61 | @echo
62 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
63 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
64 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Userena.qhcp"
65 | @echo "To view the help file:"
66 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Userena.qhc"
67 |
68 | latex:
69 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
70 | @echo
71 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
72 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
73 | "run these through (pdf)latex."
74 |
75 | changes:
76 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
77 | @echo
78 | @echo "The overview file is in $(BUILDDIR)/changes."
79 |
80 | linkcheck:
81 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
82 | @echo
83 | @echo "Link check complete; look for any errors in the above output " \
84 | "or in $(BUILDDIR)/linkcheck/output.txt."
85 |
86 | doctest:
87 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
88 | @echo "Testing of doctests in the sources finished, look at the " \
89 | "results in $(BUILDDIR)/doctest/output.txt."
90 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import migrations, models
5 | from django.conf import settings
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | dependencies = [
11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Message',
17 | fields=[
18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
19 | ('body', models.TextField(verbose_name='body')),
20 | ('sent_at', models.DateTimeField(auto_now_add=True, verbose_name='sent at')),
21 | ('sender_deleted_at', models.DateTimeField(null=True, verbose_name='sender deleted at', blank=True)),
22 | ],
23 | options={
24 | 'ordering': ['-sent_at'],
25 | 'verbose_name': 'message',
26 | 'verbose_name_plural': 'messages',
27 | },
28 | ),
29 | migrations.CreateModel(
30 | name='MessageContact',
31 | fields=[
32 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
33 | ('latest_message', models.ForeignKey(verbose_name='latest message', to='umessages.Message')),
34 | ('um_from_user', models.ForeignKey(related_name='um_from_users', verbose_name='from user', to=settings.AUTH_USER_MODEL)),
35 | ('um_to_user', models.ForeignKey(related_name='um_to_users', verbose_name='to user', to=settings.AUTH_USER_MODEL)),
36 | ],
37 | options={
38 | 'ordering': ['latest_message'],
39 | 'verbose_name': 'contact',
40 | 'verbose_name_plural': 'contacts',
41 | },
42 | ),
43 | migrations.CreateModel(
44 | name='MessageRecipient',
45 | fields=[
46 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
47 | ('read_at', models.DateTimeField(null=True, verbose_name='read at', blank=True)),
48 | ('deleted_at', models.DateTimeField(null=True, verbose_name='recipient deleted at', blank=True)),
49 | ('message', models.ForeignKey(verbose_name='message', to='umessages.Message')),
50 | ('user', models.ForeignKey(verbose_name='recipient', to=settings.AUTH_USER_MODEL)),
51 | ],
52 | options={
53 | 'verbose_name': 'recipient',
54 | 'verbose_name_plural': 'recipients',
55 | },
56 | ),
57 | migrations.AddField(
58 | model_name='message',
59 | name='recipients',
60 | field=models.ManyToManyField(related_name='received_messages', verbose_name='recipients', through='umessages.MessageRecipient', to=settings.AUTH_USER_MODEL),
61 | ),
62 | migrations.AddField(
63 | model_name='message',
64 | name='sender',
65 | field=models.ForeignKey(related_name='sent_messages', verbose_name='sender', to=settings.AUTH_USER_MODEL),
66 | ),
67 | migrations.AlterUniqueTogether(
68 | name='messagecontact',
69 | unique_together=set([('um_from_user', 'um_to_user')]),
70 | ),
71 | ]
72 |
--------------------------------------------------------------------------------
/userena/tests/tests_utils.py:
--------------------------------------------------------------------------------
1 | import sys, re, six
2 |
3 | from django.test import TestCase
4 | from django.conf import settings
5 | from django.contrib.auth import get_user_model
6 | from django.utils.six.moves.urllib_parse import urlparse, parse_qs
7 |
8 | from userena.utils import (get_gravatar, signin_redirect, get_profile_model,
9 | get_protocol, generate_sha1)
10 | from userena import settings as userena_settings
11 | from userena.compat import SiteProfileNotAvailable
12 |
13 |
14 | class UtilsTests(TestCase):
15 | """ Test the extra utils methods """
16 | fixtures = ['users']
17 |
18 | def test_generate_sha(self):
19 | s1 = six.u('\xc5se')
20 | s2 = six.u('\xd8ystein')
21 | s3 = six.u('\xc6gir')
22 | h1 = generate_sha1(s1)
23 | h2 = generate_sha1(s2)
24 | h3 = generate_sha1(s3)
25 | # Check valid SHA1 activation key
26 | self.assertTrue(re.match('^[a-f0-9]{40}$', h1[1]))
27 | self.assertTrue(re.match('^[a-f0-9]{40}$', h2[1]))
28 | self.assertTrue(re.match('^[a-f0-9]{40}$', h3[1]))
29 |
30 | def test_get_gravatar(self):
31 | template = 's=%(size)s&d=%(type)s'
32 |
33 | # Check the defaults.
34 | parsed = urlparse(get_gravatar('alice@example.com'))
35 | self.assertEqual(
36 | parse_qs(parsed.query),
37 | parse_qs(template % {'size': 80, 'type': 'identicon'})
38 | )
39 |
40 | # Check different size
41 | parsed = urlparse(get_gravatar('alice@example.com', size=200))
42 | self.assertEqual(
43 | parse_qs(parsed.query),
44 | parse_qs(template % {'size': 200, 'type': 'identicon'})
45 | )
46 |
47 | # Check different default
48 | parsed = urlparse(get_gravatar('alice@example.com', default='404'))
49 | self.assertEqual(
50 | parse_qs(parsed.query),
51 | parse_qs(template % {'size': 80, 'type': '404'})
52 | )
53 |
54 | def test_signin_redirect(self):
55 | """
56 | Test redirect function which should redirect the user after a
57 | succesfull signin.
58 |
59 | """
60 | # Test with a requested redirect
61 | self.assertEqual(signin_redirect(redirect='/accounts/'), '/accounts/')
62 |
63 | # Test with only the user specified
64 | user = get_user_model().objects.get(pk=1)
65 | self.assertEqual(signin_redirect(user=user),
66 | '/accounts/%s/' % user.username)
67 |
68 | # The ultimate fallback, probably never used
69 | self.assertEqual(signin_redirect(), settings.LOGIN_REDIRECT_URL)
70 |
71 | def test_get_profile_model(self):
72 | """
73 | Test if the correct profile model is returned when
74 | ``get_profile_model()`` is called.
75 |
76 | """
77 | # A non existent model should also raise ``SiteProfileNotAvailable``
78 | # error.
79 | with self.settings(AUTH_PROFILE_MODULE='userena.FakeProfile'):
80 | self.assertRaises(SiteProfileNotAvailable, get_profile_model)
81 |
82 | # An error should be raised when there is no ``AUTH_PROFILE_MODULE``
83 | # supplied.
84 | with self.settings(AUTH_PROFILE_MODULE=None):
85 | self.assertRaises(SiteProfileNotAvailable, get_profile_model)
86 |
87 | def test_get_protocol(self):
88 | """ Test if the correct protocol is returned """
89 | self.assertEqual(get_protocol(), 'http')
90 |
91 | with self.settings(USERENA_USE_HTTPS=True):
92 | self.assertEqual(get_protocol(), 'https')
93 |
--------------------------------------------------------------------------------
/demo/demo/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load i18n static %}
2 |
4 |
5 |
6 | {% block title %}Django Userena - Accounts for your Django application{% endblock %}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {% if messages %}
21 |
22 | {% for message in messages %}
23 | {{ message }}
24 | {% endfor %}
25 |
26 | {% endif %}
27 |
28 |
29 |
42 |
43 |
44 |
45 |
68 |
{% trans 'Fork me hard' %}
69 | {% block content_title %}{% endblock %}
70 |
71 | {% block content %}{% endblock %}
72 |
73 |
74 |
75 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/userena/contrib/umessages/managers.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.db.models import Q
3 |
4 | from userena.contrib.umessages import signals
5 |
6 | import datetime
7 |
8 | class MessageContactManager(models.Manager):
9 | """ Manager for the :class:`MessageContact` model """
10 |
11 | def get_or_create(self, um_from_user, um_to_user, message):
12 | """
13 | Get or create a Contact
14 |
15 | We override Django's :func:`get_or_create` because we want contact to
16 | be unique in a bi-directional manner.
17 |
18 | """
19 | created = False
20 | try:
21 | contact = self.get(Q(um_from_user=um_from_user, um_to_user=um_to_user) |
22 | Q(um_from_user=um_to_user, um_to_user=um_from_user))
23 |
24 | except self.model.DoesNotExist:
25 | created = True
26 | contact = self.create(um_from_user=um_from_user,
27 | um_to_user=um_to_user,
28 | latest_message=message)
29 |
30 | return (contact, created)
31 |
32 | def update_contact(self, um_from_user, um_to_user, message):
33 | """ Get or update a contacts information """
34 | contact, created = self.get_or_create(um_from_user,
35 | um_to_user,
36 | message)
37 |
38 | # If the contact already existed, update the message
39 | if not created:
40 | contact.latest_message = message
41 | contact.save()
42 | return contact
43 |
44 | def get_contacts_for(self, user):
45 | """
46 | Returns the contacts for this user.
47 |
48 | Contacts are other users that this user has received messages
49 | from or send messages to.
50 |
51 | :param user:
52 | The :class:`User` which to get the contacts for.
53 |
54 | """
55 | contacts = self.filter(Q(um_from_user=user) | Q(um_to_user=user))
56 | return contacts
57 |
58 | class MessageManager(models.Manager):
59 | """ Manager for the :class:`Message` model. """
60 |
61 | def send_message(self, sender, um_to_user_list, body):
62 | """
63 | Send a message from a user, to a user.
64 |
65 | :param sender:
66 | The :class:`User` which sends the message.
67 |
68 | :param um_to_user_list:
69 | A list which elements are :class:`User` to whom the message is for.
70 |
71 | :param message:
72 | String containing the message.
73 |
74 | """
75 | msg = self.model(sender=sender,
76 | body=body)
77 | msg.save()
78 |
79 | # Save the recipients
80 | msg.save_recipients(um_to_user_list)
81 | msg.update_contacts(um_to_user_list)
82 | signals.email_sent.send(sender=None,msg=msg)
83 |
84 | return msg
85 |
86 | def get_conversation_between(self, um_from_user, um_to_user):
87 | """ Returns a conversation between two users """
88 | messages = self.filter(Q(sender=um_from_user, recipients=um_to_user,
89 | sender_deleted_at__isnull=True) |
90 | Q(sender=um_to_user, recipients=um_from_user,
91 | messagerecipient__deleted_at__isnull=True))
92 | return messages
93 |
94 | class MessageRecipientManager(models.Manager):
95 | """ Manager for the :class:`MessageRecipient` model. """
96 |
97 | def count_unread_messages_for(self, user):
98 | """
99 | Returns the amount of unread messages for this user
100 |
101 | :param user:
102 | A Django :class:`User`
103 |
104 | :return:
105 | An integer with the amount of unread messages.
106 |
107 | """
108 | unread_total = self.filter(user=user,
109 | read_at__isnull=True,
110 | deleted_at__isnull=True).count()
111 |
112 | return unread_total
113 |
114 | def count_unread_messages_between(self, um_to_user, um_from_user):
115 | """
116 | Returns the amount of unread messages between two users
117 |
118 | :param um_to_user:
119 | A Django :class:`User` for who the messages are for.
120 |
121 | :param um_from_user:
122 | A Django :class:`User` from whom the messages originate from.
123 |
124 | :return:
125 | An integer with the amount of unread messages.
126 |
127 | """
128 | unread_total = self.filter(message__sender=um_from_user,
129 | user=um_to_user,
130 | read_at__isnull=True,
131 | deleted_at__isnull=True).count()
132 |
133 | return unread_total
134 |
--------------------------------------------------------------------------------
/demo/demo/settings.py:
--------------------------------------------------------------------------------
1 | # Django settings for Userena demo project.
2 | DEBUG = True
3 | TEMPLATE_DEBUG = DEBUG
4 |
5 | import os
6 | settings_dir = os.path.dirname(__file__)
7 | PROJECT_ROOT = os.path.abspath(os.path.dirname(settings_dir))
8 |
9 | ADMINS = (
10 | # ('Your Name', 'your_email@example.com'),
11 | )
12 |
13 | MANAGERS = ADMINS
14 |
15 | DATABASES = {
16 | 'default': {
17 | 'ENGINE': 'django.db.backends.sqlite3',
18 | 'NAME': os.path.join(PROJECT_ROOT, 'private/development.db'),
19 | }
20 | }
21 |
22 | # Internationalization
23 | TIME_ZONE = 'America/Chicago'
24 | LANGUAGE_CODE = 'en-us'
25 | ugettext = lambda s: s
26 | LANGUAGES = (
27 | ('en', ugettext('English')),
28 | ('nl', ugettext('Dutch')),
29 | ('fr', ugettext('French')),
30 | ('pl', ugettext('Polish')),
31 | ('pt', ugettext('Portugese')),
32 | ('pt-br', ugettext('Brazilian Portuguese')),
33 | ('es', ugettext('Spanish')),
34 | ('el', ugettext('Greek')),
35 | )
36 | LOCALE_PATHS = (
37 | os.path.join(PROJECT_ROOT, 'locale'),
38 | )
39 |
40 | SITE_ID = 1
41 |
42 | # If you set this to False, Django will make some optimizations so as not
43 | # to load the internationalization machinery.
44 | USE_I18N = True
45 |
46 | # If you set this to False, Django will not format dates, numbers and
47 | # calendars according to the current locale.
48 | USE_L10N = True
49 |
50 | # If you set this to False, Django will not use timezone-aware datetimes.
51 | USE_TZ = True
52 | MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'public/media/')
53 | MEDIA_URL = '/media/'
54 | STATIC_ROOT = os.path.join(PROJECT_ROOT, 'public/static/')
55 | STATIC_URL = '/static/'
56 |
57 | STATICFILES_DIRS = (
58 | os.path.join(PROJECT_ROOT, 'demo/static/'),
59 | )
60 |
61 | # List of finder classes that know how to find static files in
62 | # various locations.
63 | STATICFILES_FINDERS = (
64 | 'django.contrib.staticfiles.finders.FileSystemFinder',
65 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
66 | )
67 |
68 | # Make this unique, and don't share it with anybody.
69 | SECRET_KEY = '_g-js)o8z#8=9pr1&05h^1_#)91sbo-)g^(*=-+epxmt4kc9m#'
70 |
71 | # List of callables that know how to import templates from various sources.
72 | TEMPLATE_LOADERS = (
73 | 'django.template.loaders.filesystem.Loader',
74 | 'django.template.loaders.app_directories.Loader',
75 | # 'django.template.loaders.eggs.Loader',
76 | )
77 |
78 | MIDDLEWARE_CLASSES = (
79 | 'django.middleware.common.CommonMiddleware',
80 | 'django.contrib.sessions.middleware.SessionMiddleware',
81 | 'django.middleware.csrf.CsrfViewMiddleware',
82 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
83 | 'django.contrib.messages.middleware.MessageMiddleware',
84 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
85 | 'django.middleware.locale.LocaleMiddleware',
86 | 'userena.middleware.UserenaLocaleMiddleware',
87 | )
88 |
89 | # Add the Guardian and userena authentication backends
90 | AUTHENTICATION_BACKENDS = (
91 | 'userena.backends.UserenaAuthenticationBackend',
92 | 'guardian.backends.ObjectPermissionBackend',
93 | 'django.contrib.auth.backends.ModelBackend',
94 | )
95 |
96 | # Settings used by Userena
97 | LOGIN_REDIRECT_URL = '/accounts/%(username)s/'
98 | LOGIN_URL = '/accounts/signin/'
99 | LOGOUT_URL = '/accounts/signout/'
100 | AUTH_PROFILE_MODULE = 'profiles.Profile'
101 | USERENA_DISABLE_PROFILE_LIST = True
102 | USERENA_MUGSHOT_SIZE = 140
103 |
104 | ROOT_URLCONF = 'demo.urls'
105 | WSGI_APPLICATION = 'demo.wsgi.application'
106 |
107 | TEMPLATE_DIRS = (
108 | os.path.join(PROJECT_ROOT, 'demo/templates/'),
109 | )
110 |
111 | INSTALLED_APPS = (
112 | 'django.contrib.auth',
113 | 'django.contrib.contenttypes',
114 | 'django.contrib.sessions',
115 | 'django.contrib.sites',
116 | 'django.contrib.messages',
117 | 'django.contrib.staticfiles',
118 | 'django.contrib.admin',
119 | 'django.contrib.admindocs',
120 | 'guardian',
121 | 'south',
122 | 'userena',
123 | 'userena.contrib.umessages',
124 | 'profiles',
125 | )
126 |
127 | LOGGING = {
128 | 'version': 1,
129 | 'disable_existing_loggers': False,
130 | 'filters': {
131 | 'require_debug_false': {
132 | '()': 'django.utils.log.RequireDebugFalse'
133 | }
134 | },
135 | 'handlers': {
136 | 'mail_admins': {
137 | 'level': 'ERROR',
138 | 'filters': ['require_debug_false'],
139 | 'class': 'django.utils.log.AdminEmailHandler'
140 | }
141 | },
142 | 'loggers': {
143 | 'django.request': {
144 | 'handlers': ['mail_admins'],
145 | 'level': 'ERROR',
146 | 'propagate': True,
147 | },
148 | }
149 | }
150 |
151 | # Needed for Django guardian
152 | ANONYMOUS_USER_ID = -1
153 |
154 | # Test runner
155 | TEST_RUNNER = 'django_coverage.coverage_runner.CoverageRunner'
156 |
--------------------------------------------------------------------------------
/userena/settings.py:
--------------------------------------------------------------------------------
1 | # Userena settings file.
2 | #
3 | # Please consult the docs for more information about each setting.
4 |
5 | from django.conf import settings
6 | gettext = lambda s: s
7 |
8 |
9 | USERENA_SIGNIN_AFTER_SIGNUP = getattr(settings,
10 | 'USERENA_SIGNIN_AFTER_SIGNUP',
11 | False)
12 |
13 | USERENA_REDIRECT_ON_SIGNOUT = getattr(settings,
14 | 'USERENA_REDIRECT_ON_SIGNOUT',
15 | None)
16 |
17 | USERENA_SIGNIN_REDIRECT_URL = getattr(settings,
18 | 'USERENA_SIGNIN_REDIRECT_URL',
19 | '/accounts/%(username)s/')
20 |
21 | USERENA_ACTIVATION_REQUIRED = getattr(settings,
22 | 'USERENA_ACTIVATION_REQUIRED',
23 | True)
24 |
25 | USERENA_ACTIVATION_DAYS = getattr(settings,
26 | 'USERENA_ACTIVATION_DAYS',
27 | 7)
28 |
29 | USERENA_ACTIVATION_NOTIFY = getattr(settings,
30 | 'USERENA_ACTIVATION_NOTIFY',
31 | True)
32 |
33 | USERENA_ACTIVATION_NOTIFY_DAYS = getattr(settings,
34 | 'USERENA_ACTIVATION_NOTIFY_DAYS',
35 | 5)
36 |
37 | USERENA_ACTIVATION_RETRY = getattr(settings,
38 | 'USERENA_ACTIVATION_RETRY',
39 | False)
40 |
41 | USERENA_ACTIVATED = getattr(settings,
42 | 'USERENA_ACTIVATED',
43 | 'ALREADY_ACTIVATED')
44 |
45 | USERENA_REMEMBER_ME_DAYS = getattr(settings,
46 | 'USERENA_REMEMBER_ME_DAYS',
47 | (gettext('a month'), 30))
48 |
49 | USERENA_FORBIDDEN_USERNAMES = getattr(settings,
50 | 'USERENA_FORBIDDEN_USERNAMES',
51 | ('signup', 'signout', 'signin',
52 | 'activate', 'me', 'password'))
53 | DEFAULT_USERENA_USE_HTTPS = False
54 |
55 | # NOTE: It is only for internal use. All those settings should be refactored to only defaults
56 | # as specified in #452
57 | _USERENA_USE_HTTPS = getattr(settings, 'USERENA_USE_HTTPS', DEFAULT_USERENA_USE_HTTPS)
58 |
59 |
60 | USERENA_MUGSHOT_GRAVATAR = getattr(settings,
61 | 'USERENA_MUGSHOT_GRAVATAR',
62 | True)
63 |
64 | USERENA_MUGSHOT_GRAVATAR_SECURE = getattr(settings,
65 | 'USERENA_MUGSHOT_GRAVATAR_SECURE',
66 | _USERENA_USE_HTTPS)
67 |
68 | USERENA_MUGSHOT_DEFAULT = getattr(settings,
69 | 'USERENA_MUGSHOT_DEFAULT',
70 | 'identicon')
71 |
72 | USERENA_MUGSHOT_SIZE = getattr(settings,
73 | 'USERENA_MUGSHOT_SIZE',
74 | 80)
75 |
76 | USERENA_MUGSHOT_CROP_TYPE = getattr(settings,
77 | 'USERENA_MUGSHOT_CROP_TYPE',
78 | 'smart')
79 |
80 | USERENA_MUGSHOT_PATH = getattr(settings,
81 | 'USERENA_MUGSHOT_PATH',
82 | 'mugshots/')
83 |
84 | USERENA_DEFAULT_PRIVACY = getattr(settings,
85 | 'USERENA_DEFAULT_PRIVACY',
86 | 'registered')
87 |
88 | USERENA_DISABLE_PROFILE_LIST = getattr(settings,
89 | 'USERENA_DISABLE_PROFILE_LIST',
90 | False)
91 |
92 | USERENA_DISABLE_SIGNUP = getattr(settings,
93 | 'USERENA_DISABLE_SIGNUP',
94 | False)
95 |
96 | USERENA_USE_MESSAGES = getattr(settings,
97 | 'USERENA_USE_MESSAGES',
98 | True)
99 |
100 | USERENA_LANGUAGE_FIELD = getattr(settings,
101 | 'USERENA_LANGUAGE_FIELD',
102 | 'language')
103 |
104 | USERENA_WITHOUT_USERNAMES = getattr(settings,
105 | 'USERENA_WITHOUT_USERNAMES',
106 | False)
107 |
108 | USERENA_PROFILE_DETAIL_TEMPLATE = getattr(
109 | settings, 'USERENA_PROFILE_DETAIL_TEMPLATE', 'userena/profile_detail.html')
110 |
111 | USERENA_PROFILE_LIST_TEMPLATE = getattr(
112 | settings, 'USERENA_PROFILE_LIST_TEMPLATE', 'userena/profile_list.html')
113 |
114 | USERENA_HIDE_EMAIL = getattr(settings, 'USERENA_HIDE_EMAIL', False)
115 |
116 | USERENA_HTML_EMAIL = getattr(settings, 'USERENA_HTML_EMAIL', False)
117 |
118 | USERENA_USE_PLAIN_TEMPLATE = getattr(settings, 'USERENA_USE_PLAIN_TEMPLATE', not USERENA_HTML_EMAIL)
119 |
120 | USERENA_REGISTER_PROFILE = getattr(settings, 'USERENA_REGISTER_PROFILE', True)
121 |
122 | USERENA_REGISTER_USER = getattr(settings, 'USERENA_REGISTER_USER', True)
123 |
--------------------------------------------------------------------------------
/UPDATES.md:
--------------------------------------------------------------------------------
1 | # UPDATES
2 |
3 | This file contains all the backwards-incompatible (since 1.0.1) and other
4 | significant (since 1.4.1) changes.
5 |
6 |
7 | ## Version 2.0.1
8 |
9 | Fixes and improvements:
10 |
11 | - Fixed missing `README.md` that caused installation failure (#517).
12 | - Use universal wheels when distributing the package.
13 |
14 | ## Version 2.0.0
15 |
16 | Backwards incompatible changes:
17 |
18 | - Drop support for Django 1.4
19 | - Add support for Django 1.9
20 | - Added new Django 1.9 migration for `userena` and `userena.contrib.umessages`.
21 | Old South migrations are still available in `userena.south_migrations` and
22 | `userena.contrib.umessages.south_migrations` but are deprecated and may be
23 | removed in future major releases. `South>1.0` is required for older versions
24 | of Django.
25 | - `django-guardian` requirement bumped to `<=1.4.1` as newer version (1.4.2)
26 | fails on tests.
27 | - removed all `{% load url from future %}` from userena templates for
28 | compatibility with Django 1.9
29 | - `sha_constructor()`, `smart_text()`, and `md5_constructor` removed from
30 | `userena.compat`
31 | - Use simple list literal as url patterns instead of
32 | `django.conf.urls.patterns()` function
33 |
34 | Deprecated features:
35 |
36 | - `userena.utils.get_user_model` is deprecated and will be removed in version
37 | 3.0.0. Use `django.contrib.auth.get_user_model`
38 |
39 | Fixes and improvements:
40 |
41 | - More nodes added to the travis test matrix.
42 | - Some documentation moved to `.md` files.
43 | - Deprecated unittest aliases replaced in tests.
44 | - Updated list of trove classifiers.
45 | - Added option to execute `runtests` script with pattern parameter to limit
46 | number of tests.
47 | - Added `{posargs}` to test command execution in `tox.ini` to improve developer
48 | experience during tests.
49 | - Minor documentation improvements.
50 | - Set `django-guardian<1.4.0` version for Django 1.5 and 1.6 tests.
51 |
52 |
53 | ## Version 1.5.1
54 |
55 | - Update url patterns to handle usernames starting with 'signup', 'signin',
56 | 'signout' strings (#502)
57 |
58 | ## Version 1.5.0
59 |
60 | Fixes and improvements:
61 |
62 | - Updated Norwegian translations and fixed issue with unicode characters sent
63 | to `utils.generate_sha1` (#472)
64 | - Fix `upload_to_mugshot` if model uses primary key diffrent than `id` (#475)
65 | - Minor compatibility improvements (#485)
66 | - Refactored mailer (#486)
67 | - Password reset email now inlcudes email template for django>=1.7 (#493)
68 | - Fixes to translations (#499)
69 | - Added Romanian translations (#500)
70 |
71 | Backwards incompatible changes:
72 |
73 | - django-guardian has version fixed to `<=1.3.1` due to django 1.4 compatibility
74 |
75 |
76 | ## Version 1.4.1
77 |
78 | Fixes and improvements:
79 |
80 | - Set `html2text==2014.12.29` requirement in order to remove nasty hack
81 | in `userena/mail.py` (#462).
82 | - Updated version in Sphinx docs (#461).
83 | - Added support for Python3.2 (#453).
84 | - Fixed issue causing malformed utf-8 usernames on user profile creation (#456).
85 | - Fixed issue with user creation on Python3.4 (#455).
86 | - Fixed randomly failing tests on travis (#446).
87 |
88 |
89 | ## Version 1.4.0
90 |
91 | Fixes and improvements:
92 |
93 | - Python3.3 and Python3.4 support added.
94 |
95 |
96 | ## Version 1.3.2
97 |
98 | Backwards incompatible changes:
99 |
100 | - Creating new user always creates new empty userena profile (fixes bug from 1.3.1).
101 |
102 |
103 | ## Version 1.3.1
104 |
105 | Backwards incompatible changes:
106 |
107 | - When USERENA_ACTIVATION_REQUIRED = False, creating new user does not automatically
108 | create userena profile (bug).
109 |
110 |
111 | ## Version 1.2.2
112 |
113 | Backwards incompatible changes:
114 |
115 | - Changed backwards relationships names for Umessages contrib application from
116 | `to_user` to `um_to_user` and `from_user` to `um_from_user`.
117 |
118 |
119 | ## Version 1.2.0
120 |
121 | Backwards incompatible changes:
122 |
123 | - This version updates Userena to be able to use the new `User` model found in
124 | Django 1.5. This does require the django-guardion > 1.5-dev. To make this
125 | work, there is a `get_user_model` function in `userena/utils.py` which is used
126 | to get the correct `User` model.
127 |
128 |
129 | ## Version 1.1.2
130 |
131 | Backwards incompatible changes:
132 |
133 | - Activation view no longer contains username. If you override
134 | `userena/templates/userena/emails/activation_email_message.txt` and
135 | `userena/templates/userena/emails/confirmation_email_message_new.txt` be sure
136 | to remove username from the URL.
137 |
138 |
139 | ## Version 1.1
140 |
141 | Backwards incompatible changes:
142 |
143 | - Userena now requires Django >= 1.3 because of the use of class based views.
144 |
145 |
146 | ## Version 1.0.1
147 |
148 | Backwards incompatible changes:
149 |
150 | - Removed the ``user`` relationship outside ``UserenaBaseProfile`` model. This
151 | allows the user to define it's own relationship and fixes a conflict while
152 | running ``manage.py test``. To migrate, place the ``user`` field inside your
153 | profile model, instead of inheriting it from the ``abstract`` class.
154 |
--------------------------------------------------------------------------------
/userena/tests/test_commands.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | from __future__ import unicode_literals
3 |
4 | from django.test import TestCase
5 | from django.core.management import call_command
6 | from django.contrib.auth import get_user_model
7 | from django.contrib.auth.models import Permission
8 | from django.contrib.contenttypes.models import ContentType
9 |
10 | from userena.models import UserenaSignup
11 | from userena.managers import ASSIGNED_PERMISSIONS
12 | from userena import settings as userena_settings
13 | from userena.utils import get_profile_model
14 |
15 | from guardian.models import UserObjectPermission
16 |
17 | import datetime
18 |
19 | User = get_user_model()
20 |
21 | class CleanExpiredTests(TestCase):
22 | user_info = {'username': 'alice',
23 | 'password': 'swordfish',
24 | 'email': 'alice@example.com'}
25 |
26 | def test_clean_expired(self):
27 | """
28 | Test if ``clean_expired`` deletes all users which ``activation_key``
29 | is expired.
30 |
31 | """
32 | # Create an account which is expired.
33 | user = UserenaSignup.objects.create_user(**self.user_info)
34 | user.date_joined -= datetime.timedelta(days=userena_settings.USERENA_ACTIVATION_DAYS + 1)
35 | user.save()
36 |
37 | # There should be one account now
38 | User.objects.get(username=self.user_info['username'])
39 |
40 | # Clean it.
41 | call_command('clean_expired')
42 |
43 | self.assertEqual(User.objects.filter(username=self.user_info['username']).count(), 0)
44 |
45 | class CheckPermissionTests(TestCase):
46 | user_info = {'username': 'alice',
47 | 'password': 'swordfish',
48 | 'email': 'alice@example.com'}
49 |
50 | def test_check_permissions(self):
51 | # Create a new account.
52 | user = UserenaSignup.objects.create_user(**self.user_info)
53 | user.save()
54 |
55 | # Remove all permissions
56 | UserObjectPermission.objects.filter(user=user).delete()
57 | self.assertEqual(UserObjectPermission.objects.filter(user=user).count(),
58 | 0)
59 |
60 | # Check it
61 | call_command('check_permissions')
62 |
63 | # User should have all permissions again
64 | user_permissions = UserObjectPermission.objects.filter(user=user).values_list('permission__codename', flat=True)
65 |
66 | required_permissions = ['change_user', 'delete_user', 'change_profile', 'view_profile']
67 | for perm in required_permissions:
68 | if perm not in user_permissions:
69 | self.fail()
70 |
71 | # Check it again should do nothing
72 | call_command('check_permissions', test=True)
73 |
74 | def test_incomplete_permissions(self):
75 | # Delete the neccesary permissions
76 | profile_model_obj = get_profile_model()
77 | content_type_profile = ContentType.objects.get_for_model(profile_model_obj)
78 | content_type_user = ContentType.objects.get_for_model(User)
79 | for model, perms in ASSIGNED_PERMISSIONS.items():
80 | if model == "profile":
81 | content_type = content_type_profile
82 | else: content_type = content_type_user
83 | for perm in perms:
84 | Permission.objects.get(name=perm[1],
85 | content_type=content_type).delete()
86 |
87 | # Check if they are they are back
88 | for model, perms in ASSIGNED_PERMISSIONS.items():
89 | if model == "profile":
90 | content_type = content_type_profile
91 | else: content_type = content_type_user
92 | for perm in perms:
93 | try:
94 | perm = Permission.objects.get(name=perm[1],
95 | content_type=content_type)
96 | except Permission.DoesNotExist: pass
97 | else: self.fail("Found %s: " % perm)
98 |
99 | # Repair them
100 | call_command('check_permissions', test=True)
101 |
102 | # Check if they are they are back
103 | for model, perms in ASSIGNED_PERMISSIONS.items():
104 | if model == "profile":
105 | content_type = content_type_profile
106 | else: content_type = content_type_user
107 | for perm in perms:
108 | try:
109 | perm = Permission.objects.get(name=perm[1],
110 | content_type=content_type)
111 | except Permission.DoesNotExist:
112 | self.fail()
113 |
114 | def test_no_profile(self):
115 | """ Check for warning when there is no profile """
116 | # TODO: Dirty! Currently we check for the warning by getting a 100%
117 | # test coverage, meaning that it dit output some warning.
118 | user = UserenaSignup.objects.create_user(**self.user_info)
119 |
120 | # remove the profile of this user
121 | get_profile_model().objects.get(user=user).delete()
122 |
123 | # run the command to check for the warning.
124 | call_command('check_permissions', test=True)
125 |
126 |
--------------------------------------------------------------------------------
/userena/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url
2 | from django.contrib.auth import views as auth_views
3 |
4 | from userena import settings as userena_settings
5 | from userena import views as userena_views
6 | from userena.compat import auth_views_compat_quirks, password_reset_uid_kwarg
7 |
8 |
9 | def merged_dict(dict_a, dict_b):
10 | """Merges two dicts and returns output. It's purpose is to ease use of
11 | ``auth_views_compat_quirks``
12 | """
13 | dict_a.update(dict_b)
14 | return dict_a
15 |
16 | urlpatterns = [
17 | # Signup, signin and signout
18 | url(r'^signup/$',
19 | userena_views.signup,
20 | name='userena_signup'),
21 | url(r'^signin/$',
22 | userena_views.signin,
23 | name='userena_signin'),
24 | url(r'^signout/$',
25 | userena_views.signout,
26 | name='userena_signout'),
27 |
28 | # Reset password
29 | url(r'^password/reset/$',
30 | auth_views.password_reset,
31 | merged_dict({'template_name': 'userena/password_reset_form.html',
32 | 'email_template_name': 'userena/emails/password_reset_message.txt',
33 | 'extra_context': {'without_usernames': userena_settings.USERENA_WITHOUT_USERNAMES}
34 | }, auth_views_compat_quirks['userena_password_reset']),
35 | name='userena_password_reset'),
36 | url(r'^password/reset/done/$',
37 | auth_views.password_reset_done,
38 | {'template_name': 'userena/password_reset_done.html',},
39 | name='userena_password_reset_done'),
40 | url(r'^password/reset/confirm/(?P<%s>[0-9A-Za-z]+)-(?P.+)/$' % password_reset_uid_kwarg,
41 | auth_views.password_reset_confirm,
42 | merged_dict({'template_name': 'userena/password_reset_confirm_form.html',
43 | }, auth_views_compat_quirks['userena_password_reset_confirm']),
44 | name='userena_password_reset_confirm'),
45 | url(r'^password/reset/confirm/complete/$',
46 | auth_views.password_reset_complete,
47 | {'template_name': 'userena/password_reset_complete.html'},
48 | name='userena_password_reset_complete'),
49 |
50 | # Signup
51 | url(r'^(?P[\@\.\+\w-]+)/signup/complete/$',
52 | userena_views.direct_to_user_template,
53 | {'template_name': 'userena/signup_complete.html',
54 | 'extra_context': {'userena_activation_required': userena_settings.USERENA_ACTIVATION_REQUIRED,
55 | 'userena_activation_days': userena_settings.USERENA_ACTIVATION_DAYS}},
56 | name='userena_signup_complete'),
57 |
58 | # Invite
59 | url(r'^invite/$',userena_views.invite_new_user,{'template_name':'userena/invite_new_user.html','success_url':'userena_list_invited_users'},
60 | name='userena_invite_new_user'),
61 | url(r'^invited-users/$',userena_views.list_invited_users,{'template_name':'userena/list_invited_users.html'},name='userena_list_invited_users'),
62 | url(r'^invite/(?P\w+)/$',
63 | userena_views.activate_invited_user,
64 | name='userena_activate_invited_user'),
65 |
66 |
67 |
68 | # Activate
69 | url(r'^activate/(?P\w+)/$',
70 | userena_views.activate,
71 | name='userena_activate'),
72 |
73 | # Retry activation
74 | url(r'^activate/retry/(?P\w+)/$',
75 | userena_views.activate_retry,
76 | name='userena_activate_retry'),
77 |
78 | # Change email and confirm it
79 | url(r'^(?P[\@\.\+\w-]+)/email/$',
80 | userena_views.email_change,
81 | name='userena_email_change'),
82 | url(r'^(?P[\@\.\+\w-]+)/email/complete/$',
83 | userena_views.direct_to_user_template,
84 | {'template_name': 'userena/email_change_complete.html'},
85 | name='userena_email_change_complete'),
86 | url(r'^(?P[\@\.\+\w-]+)/confirm-email/complete/$',
87 | userena_views.direct_to_user_template,
88 | {'template_name': 'userena/email_confirm_complete.html'},
89 | name='userena_email_confirm_complete'),
90 | url(r'^confirm-email/(?P\w+)/$',
91 | userena_views.email_confirm,
92 | name='userena_email_confirm'),
93 |
94 | # Disabled account
95 | url(r'^(?P[\@\.\+\w-]+)/disabled/$',
96 | userena_views.disabled_account,
97 | {'template_name': 'userena/disabled.html'},
98 | name='userena_disabled'),
99 |
100 | # Change password
101 | url(r'^(?P[\@\.\+\w-]+)/password/$',
102 | userena_views.password_change,
103 | name='userena_password_change'),
104 | url(r'^(?P[\@\.\+\w-]+)/password/complete/$',
105 | userena_views.direct_to_user_template,
106 | {'template_name': 'userena/password_complete.html'},
107 | name='userena_password_change_complete'),
108 |
109 | # Edit profile
110 | url(r'^(?P[\@\.\+\w-]+)/edit/$',
111 | userena_views.profile_edit,
112 | name='userena_profile_edit'),
113 |
114 | # View profiles
115 | url(r'^(?P(?!(signout|signup|signin)/)[\@\.\+\w-]+)/$',
116 | userena_views.profile_detail,
117 | name='userena_profile_detail'),
118 | url(r'^page/(?P[0-9]+)/$',
119 | userena_views.ProfileListView.as_view(),
120 | name='userena_profile_list_paginated'),
121 | url(r'^$',
122 | userena_views.ProfileListView.as_view(),
123 | name='userena_profile_list'),
124 | ]
125 |
--------------------------------------------------------------------------------
/userena/south_migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | import datetime
3 | from south.db import db
4 | from south.v2 import SchemaMigration
5 | from django.db import models
6 | from userena.utils import user_model_label
7 |
8 | class Migration(SchemaMigration):
9 |
10 | def forwards(self, orm):
11 |
12 | # Adding model 'UserenaSignup'
13 | db.create_table('userena_userenasignup', (
14 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
15 | ('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='userena_signup', unique=True, to=orm[user_model_label])),
16 | ('last_active', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
17 | ('activation_key', self.gf('django.db.models.fields.CharField')(max_length=40, blank=True)),
18 | ('activation_notification_send', self.gf('django.db.models.fields.BooleanField')(default=False)),
19 | ('email_unconfirmed', self.gf('django.db.models.fields.EmailField')(max_length=75, blank=True)),
20 | ('email_confirmation_key', self.gf('django.db.models.fields.CharField')(max_length=40, blank=True)),
21 | ('email_confirmation_key_created', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
22 | ))
23 | db.send_create_signal('userena', ['UserenaSignup'])
24 |
25 |
26 | def backwards(self, orm):
27 |
28 | # Deleting model 'UserenaSignup'
29 | db.delete_table('userena_userenasignup')
30 |
31 |
32 | models = {
33 | 'auth.group': {
34 | 'Meta': {'object_name': 'Group'},
35 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
36 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
37 | 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
38 | },
39 | 'auth.permission': {
40 | 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
41 | 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
42 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
43 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
44 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
45 | },
46 | user_model_label: {
47 | 'Meta': {'object_name': user_model_label.split('.')[-1]},
48 | 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
49 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
50 | 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
51 | 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
52 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
53 | 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
54 | 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
55 | 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
56 | 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
57 | 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
58 | 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
59 | 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
60 | 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
61 | },
62 | 'contenttypes.contenttype': {
63 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
64 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
65 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
66 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
67 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
68 | },
69 | 'userena.userenasignup': {
70 | 'Meta': {'object_name': 'UserenaSignup'},
71 | 'activation_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
72 | 'activation_notification_send': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
73 | 'email_confirmation_key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}),
74 | 'email_confirmation_key_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
75 | 'email_unconfirmed': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
76 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
77 | 'last_active': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
78 | 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'userena_signup'", 'unique': 'True', 'to': "orm['%s']" % user_model_label})
79 | }
80 | }
81 |
82 | complete_apps = ['userena']
83 |
--------------------------------------------------------------------------------