Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[users] app added

  • Loading branch information...
commit 98498a91d72821de6045f56ebed8cbd9ae7f8f40 1 parent fa4087d
@josezambrana josezambrana authored
Showing with 1,804 additions and 0 deletions.
  1. +24 −0 users/__init__.py
  2. +44 −0 users/admin.py
  3. +51 −0 users/fixtures/users.json
  4. +289 −0 users/forms.py
  5. +49 −0 users/listeners.py
  6. +49 −0 users/managers.py
  7. +123 −0 users/models.py
  8. BIN  users/static/avatars/avatar_l.png
  9. BIN  users/static/avatars/avatar_m.png
  10. BIN  users/static/avatars/avatar_s.png
  11. BIN  users/static/avatars/avatar_u.png
  12. BIN  users/static/bg.jpg
  13. +22 −0 users/templates/form.users.login.html
  14. +16 −0 users/templates/form.users.register.html
  15. +5 −0 users/templates/form.users.settings.html
  16. +25 −0 users/templates/mail.password_instructions.html
  17. +18 −0 users/templates/mail.password_instructions.txt
  18. +42 −0 users/templates/mail.welcome.html
  19. +28 −0 users/templates/mail.welcome.txt
  20. +15 −0 users/templates/page.password_reset.complete.html
  21. +35 −0 users/templates/page.password_reset.confirm.html
  22. +12 −0 users/templates/page.password_reset.done.html
  23. +31 −0 users/templates/page.password_reset.html
  24. +33 −0 users/templates/page.users.index.html
  25. +11 −0 users/templates/page.users.login.html
  26. +39 −0 users/templates/page.users.profile.html
  27. +11 −0 users/templates/page.users.register.html
  28. +31 −0 users/templates/page.users.settings.html
  29. +377 −0 users/tests.py
  30. +67 −0 users/urls.py
  31. +62 −0 users/utils.py
  32. +295 −0 users/views.py
View
24 users/__init__.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mandla Web Studio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'Jose Maria Zambrana Arze'
+__email__ = 'contact@josezambrana.com'
+__version__ = '0.1'
+__copyright__ = 'Copyright 2012, Mandla Web Studio'
+
+
+# Signals
+from users.listeners import create_profile
View
44 users/admin.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mandla Web Studio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'Jose Maria Zambrana Arze'
+__email__ = 'contact@josezambrana.com'
+__version__ = '0.1'
+__copyright__ = 'Copyright 2012, Mandla Web Studio'
+
+
+from django.contrib import admin
+
+from users.models import Profile
+
+
+class ProfileAdmin(admin.ModelAdmin):
+ """
+ Configuración para el área de administración para el modelo Profile.
+ """
+
+ list_display = ('user', )
+ readonly_fields = ('extras', )
+
+ def has_add_permission(self, request):
+ """
+ Evita que el usuario agregue nuevos usuarios desde el administrador del
+ perfil, ya que debe hacerse solo desde el administrador del usuario.
+ """
+ return False
+
+admin.site.register(Profile, ProfileAdmin)
+
View
51 users/fixtures/users.json
@@ -0,0 +1,51 @@
+[
+ { "fields" : {
+ "date_joined" : "2011-03-16 20:57:56",
+ "email" : "contact@josezambrana.com",
+ "first_name" : "Jose Maria",
+ "groups" : [ ],
+ "is_active" : true,
+ "is_staff" : true,
+ "is_superuser" : true,
+ "last_login" : "2011-03-30 20:42:22",
+ "last_name" : "Zambrana",
+ "password" : "sha1$4b14b$1875933b7fc6a7f33f88326afae907e914662cc0",
+ "user_permissions" : [ ],
+ "username" : "admin"
+ },
+ "model" : "auth.user",
+ "pk" : 1
+ },
+ { "fields" : { "date_joined" : "2011-03-30 02:54:48",
+ "email" : "test@jumppe.com",
+ "first_name" : "Testac",
+ "groups" : [ ],
+ "is_active" : true,
+ "is_staff" : false,
+ "is_superuser" : false,
+ "last_login" : "2011-03-31 01:10:41",
+ "last_name" : "Count",
+ "password" : "sha1$2f760$df5b87c1ec8ddc1045673b75433aa8d04455b562",
+ "user_permissions" : [ ],
+ "username" : "testaccount"
+ },
+ "model" : "auth.user",
+ "pk" : 2
+ },
+ { "fields" : { "date_joined" : "2011-04-06 23:12:54",
+ "email" : "zero.fuxor@gmail.com",
+ "first_name" : "Zero",
+ "groups" : [ ],
+ "is_active" : true,
+ "is_staff" : false,
+ "is_superuser" : false,
+ "last_login" : "2011-04-06 23:12:54",
+ "last_name" : "Fuxor",
+ "password" : "sha1$4b14b$1875933b7fc6a7f33f88326afae907e914662cc0",
+ "user_permissions" : [ ],
+ "username" : "zero"
+ },
+ "model" : "auth.user",
+ "pk" : 3
+ }
+]
View
289 users/forms.py
@@ -0,0 +1,289 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mandla Web Studio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'Jose Maria Zambrana Arze'
+__email__ = 'contact@josezambrana.com'
+__version__ = '0.1'
+__copyright__ = 'Copyright 2012, Mandla Web Studio'
+
+
+import logging
+import re
+
+from django import forms
+from django.core.exceptions import MultipleObjectsReturned
+from django.forms.widgets import Widget
+from django.forms.fields import Field
+from django.conf import settings
+from django.forms import ModelForm
+
+from django.contrib.auth.models import User
+from django.contrib.auth.forms import UserCreationForm
+from django.contrib.auth.forms import PasswordResetForm
+from django.contrib.localflavor.es import forms as es_forms
+from django.contrib.auth.tokens import default_token_generator
+
+from django.contrib.sites.models import Site, get_current_site
+
+from django.utils.translation import ugettext_lazy as _
+from django.utils.http import int_to_base36
+from django.utils.safestring import mark_safe
+from django.utils.encoding import force_unicode
+
+from common.mail import Mailer
+
+from thumbnails.templatetags.thumbnails_tags import thumbnail_url
+from thumbnails.forms import ThumbnailField
+from thumbnails.utils import validate_file_size
+
+from users.models import Profile
+
+
+UPPER_RE = re.compile('[A-Z]+')
+USERNAME_RE = re.compile(r'^[a-z0-9\-]+$')
+
+
+current_site = Site.objects.get_current()
+
+
+class MixinClean(object):
+ """
+ Clase para encapsular las validaciones relacionadas con los campos
+ de un usuario.
+ """
+
+ def clean_password2(self):
+ password1 = self.cleaned_data.get("password1", "")
+ password2 = self.cleaned_data["password2"]
+ if password1 != password2:
+ raise forms.ValidationError(_("The two password fields didn't match."))
+ return password2
+
+ def clean_email(self):
+ email = self.cleaned_data.get('email')
+
+ if not email:
+ raise forms.ValidationError(_(u'El email es obligatorio'))
+
+ username = self.cleaned_data.get('username')
+ if email and User.objects.filter(email=email).exclude(username=username).count():
+ raise forms.ValidationError(u'El email debe ser único.')
+
+ return email
+
+ def clean_first_name(self):
+ first_name = self.cleaned_data.get('first_name')
+
+ if not first_name:
+ raise forms.ValidationError(_(u'El Nombre es obligatorio'))
+
+ return first_name
+
+ def clean_last_name(self):
+ last_name = self.cleaned_data.get('last_name')
+
+ if not last_name:
+ raise forms.ValidationError(_(u'Los Apellidos son obligatorios.'))
+
+ return last_name
+
+ def save(self, commit=True):
+ user = super(UserCreationForm, self).save(commit=False)
+ user.set_password(self.cleaned_data["password1"])
+ if commit:
+ user.save()
+ return user
+
+
+class RegisterForm(UserCreationForm, MixinClean):
+ """
+ Formulario para registrar un usuario en el sistema
+ """
+
+ class Meta:
+ model = UserCreationForm.Meta.model
+ fields = ("username", "first_name", "last_name", "email")
+
+ def __init__(self, *args, **kwargs):
+ UserCreationForm.__init__(self, *args, **kwargs)
+ self.fields.keyOrder = ["first_name", "last_name", "username",
+ "password1", "password2", "email"]
+ self.fields['first_name'].label = _('Nombre')
+ self.fields['last_name'].label = _('Apellidos')
+ self.fields['email'].label = _('Email')
+ self.fields['username'].help_text = ""
+ self.fields['password2'].help_text = ""
+ self.fields['email'].label = _('Email')
+
+ def clean_username(self):
+ """
+ Valida el nombre de usuario.
+ """
+
+ username = self.cleaned_data.get('username')
+
+ if not USERNAME_RE.match(username):
+ raise forms.ValidationError(_(u"Solo se permiten caracteres alfanuméricos y el caracter -"))
+
+ return username.lower()
+
+ def save(self, *args, **kwargs):
+ """
+ Almacena en la base de datos el nuevo usuario y envia un mail de
+ bienvenida.
+ """
+
+ # Crea el usuario
+ user = super(RegisterForm, self).save(*args, **kwargs)
+
+ # Envia el mail de bienvenida.
+ context = {
+ 'user': user,
+ }
+ subject = _(u'%(firstname)s Bienvenido a %(sitename)s') % ({
+ 'firstname': user.first_name,
+ 'sitename': current_site.name
+ })
+
+ mail = Mailer(subject, 'mail.welcome.txt',
+ 'mail.welcome.html',
+ **context)
+ mail.send(user.email)
+
+ return user
+
+
+class UserForm(forms.ModelForm, MixinClean):
+ """
+ Formulario para configurar los datos del usuario: nombres, apellidos, email
+ contraseña.
+ """
+
+ password1 = forms.CharField(label=_("Password"),
+ widget=forms.PasswordInput,
+ required=False)
+
+ password2 = forms.CharField(label=_("Password confirmation"),
+ widget=forms.PasswordInput,
+ help_text=_("Enter the same password as above,"
+ " for verification."),
+ required=False)
+
+ class Meta:
+ model = User
+ fields = ("first_name", "last_name", "email")
+
+ def __init__(self, *args, **kwargs):
+ super(UserForm, self).__init__(*args, **kwargs)
+ self.fields.keyOrder = ["first_name", "last_name",
+ "password1", "password2", "email"]
+ self.fields['first_name'].label = _('Nombre')
+ self.fields['last_name'].label = _('Apellidos')
+ self.fields['password2'].help_text = ""
+
+ def clean_email(self):
+ email = self.cleaned_data.get('email')
+
+ if not email:
+ raise forms.ValidationError(_(u'El email es obligatorio'))
+
+ username = self.instance.username
+
+ if email and User.objects.filter(email=email).exclude(username=username).exists():
+ raise forms.ValidationError(u'El email debe ser único.')
+
+ return email
+
+ def save(self, commit=True):
+ user = super(UserForm, self).save(commit=False)
+
+ if self.cleaned_data["password1"]:
+ user.set_password(self.cleaned_data["password1"])
+
+ if commit:
+ user.save()
+
+ return user
+
+
+class ProfileForm(forms.ModelForm):
+ """
+ Formulario para configurar el perfil del usuario.
+ """
+
+ image = ThumbnailField(label=_("Avatar"), required=False, help_text=u'Tamaño máximo 5Mb')
+
+ class Meta:
+ model = Profile
+ fields = ('image', 'url', 'description')
+
+ def clean_image(self):
+ image = self.cleaned_data.get('image')
+
+ if image is not None:
+ validate_file_size(image)
+
+ return image
+
+ def save(self):
+ image = self.cleaned_data.get('image')
+
+ profile = super(ProfileForm, self).save(commit=True)
+
+ if image is not None:
+ profile.create_thumbnails()
+
+ return profile
+
+
+class DesignForm(forms.ModelForm):
+ """
+ Formulario para configurar el diseño del perfil de un usuario.
+ """
+
+ class Meta:
+ model = Profile
+ fields = ('background', 'background_color', 'links_color',
+ 'button_background', 'button_color')
+
+
+class PasswordResetForm(PasswordResetForm):
+ def save(self, **kwargs):
+ use_https = kwargs.pop('use_https', False)
+ token_generator = kwargs.pop('token_generator', default_token_generator)
+ request = kwargs.pop('request', None)
+ from_email = kwargs.pop('from_email', None)
+
+ for user in self.users_cache:
+ site_name = current_site.name
+ domain = current_site.domain
+
+ subject = _("Password reset on %s") % site_name
+ context = {
+ 'email': user.email,
+ 'domain': domain,
+ 'site_name': site_name,
+ 'uid': int_to_base36(user.id),
+ 'user': user,
+ 'token': token_generator.make_token(user),
+ 'protocol': use_https and 'https' or 'http',
+ }
+
+ mail = Mailer(subject, 'mail.password_instructions.txt',
+ 'mail.password_instructions.html',
+ **context)
+ mail.send(user.email, from_email)
+
View
49 users/listeners.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mandla Web Studio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'Jose Maria Zambrana Arze'
+__email__ = 'contact@josezambrana.com'
+__version__ = '0.1'
+__copyright__ = 'Copyright 2012, Mandla Web Studio'
+
+
+import logging
+
+from django.db import transaction
+from django.dispatch import receiver
+from django.contrib.auth.models import User
+from django.db.models.signals import post_save
+from django.contrib.auth.signals import user_logged_in
+from django.contrib import messages
+
+
+@receiver(post_save, sender=User)
+def create_profile(sender, instance, created, using, *args, **kwargs):
+ """
+ Crea el perfil de usuario cuando se crea un usuario.
+ """
+ from users.models import Profile
+
+ if created:
+ try:
+ sp_id = transaction.savepoint()
+ profile = Profile(user=instance, username=instance.username)
+ profile.save()
+ transaction.savepoint_commit(sp_id)
+ except Exception, e:
+ logging.error('ERROR: %s ' % e)
+ transaction.savepoint_rollback(sp_id)
+
View
49 users/managers.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mandla Web Studio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'Jose Maria Zambrana Arze'
+__email__ = 'contact@josezambrana.com'
+__version__ = '0.1'
+__copyright__ = 'Copyright 2012, Mandla Web Studio'
+
+
+from django.db import models
+from django.db.models import Manager
+from django.template.defaultfilters import slugify
+
+
+class ProfileManager(Manager):
+ """
+ Manejador de objetos de perfil.
+ """
+
+ def profiles_dict(self, users_ids):
+ """
+ Retorna un diccionario donde las claves son los ids de los usuarios
+ y los valores sus respectivos objetos.
+ """
+
+ profiles_dict = {}
+
+ profiles = self.filter(user__pk__in=users_ids)
+
+ for profile in profiles:
+ key = str(profile.user_id)
+ profiles_dict[key] = profile
+
+ return profiles_dict
+
+
View
123 users/models.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mandla Web Studio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'Jose Maria Zambrana Arze'
+__email__ = 'contact@josezambrana.com'
+__version__ = '0.1'
+__copyright__ = 'Copyright 2012, Mandla Web Studio'
+
+
+import os
+import uuid
+import logging
+
+from time import time
+
+from django.db import models
+from django.conf import settings
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
+from django.contrib.contenttypes.models import ContentType
+
+from django.utils.translation import ugettext_lazy as _
+
+from common.fields import DictField
+from common.fields import ColorField
+from thumbnails.models import ThumbnailMixin
+
+from users.managers import ProfileManager
+
+
+current_site = Site.objects.get_current()
+
+
+class Profile(ThumbnailMixin):
+ """
+ Modelo para manejar información adicional sobre el perfil del usuario.
+ """
+
+ #: usuario al que pertence el perfil
+ user = models.ForeignKey(User, related_name='profile')
+
+ #: Nombre de usuario
+ username = models.CharField(_(u'Nombre de usuario'), max_length=255,
+ blank=True,
+ null=True)
+
+ # Fecha de la última publicación realizada.
+ last_published = models.DateTimeField(_(u'Última publicación'), auto_now_add=True)
+
+ #: descripción del usuario.
+ description = models.TextField(_(u'Descripción'), blank=True, default='')
+
+ #: Website del usuario.
+ url = models.URLField(_(u'Website'), blank=True, default='')
+
+ #: Campo para almacenar información adicional
+ extras = DictField(_('Extras'), null=False, blank=True, default={})
+
+ #: Imagen de fondo del perfil.
+ background = models.ImageField(_(u'Imagen de fondo'), blank=True, null=True, upload_to='backgrounds')
+
+ #: Color de fondo del perfil.
+ background_color = ColorField(_(u'Color de fondo'), blank=True, null=True)
+
+ #: Color de los enlaces.
+ links_color = ColorField(_(u'Color de los enlaces'), blank=True, null=True)
+
+ #: Color del fondo de los botones
+ button_background = ColorField(_(u'Fondo botones'), blank=True, null=True)
+
+ #: Color del texto de los botones
+ button_color = ColorField(_(u'Color texto botones'), blank=True, null=True)
+
+
+ objects = ProfileManager()
+
+ #: Los tamaños permitidos en los avatares
+ sizes = {
+ 'u': (25, 25),
+ 's': (50, 50),
+ 'm': (80, 80),
+ 'l': (115, 115),
+ }
+
+ # directorio base para los avatares
+ basepath = 'avatars'
+
+ #: Avatares por defecto.
+ defaults = {
+ 'u': 'avatar_u.png',
+ 's': 'avatar_s.png',
+ 'm': 'avatar_m.png',
+ 'l': 'avatar_l.png',
+ }
+
+ def thumbnail_name(self, size):
+ """
+ Retorna el nombre del thumbnail del tamaño *size*
+ """
+
+ thumb_name = '%s_%s.jpg' % (self.user.username, str(size))
+ return os.path.join(self.thumbnail_basepath(), thumb_name)
+
+ def get_absolute_url(self):
+ """
+ Retorna el path absoluto del perfil.
+ """
+
+ return "http://%s.%s" % (self.username, current_site.domain)
View
BIN  users/static/avatars/avatar_l.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  users/static/avatars/avatar_m.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  users/static/avatars/avatar_s.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  users/static/avatars/avatar_u.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  users/static/bg.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
22 users/templates/form.users.login.html
@@ -0,0 +1,22 @@
+{% extends 'forms/form.html' %}
+{% load i18n %}
+
+{% block beforeform%}
+ <div class="social-fb highlight corner-small clearfix">
+ <a href="{% url socialauth_login %}"
+ class="button">Con&eacute;ctate con Facebook</a>
+ </div>
+ <p class="login-help-text">
+ ...o con tu cuenta webeando.me:
+ </p>
+{% endblock %}
+
+{% block class_form %}users-login{% endblock %}
+
+{% block button %}{% trans 'Entrar' %}{% endblock %}
+
+{% block afterbutton %}
+ <span id="lost-password" class="lost-password float-right">
+ <a href="{% url password_reset %}">Perdiste tu contrase&ntilde;a?</a>
+ </span>
+{% endblock %}
View
16 users/templates/form.users.register.html
@@ -0,0 +1,16 @@
+{% extends 'forms/form.html' %}
+{% load i18n %}
+
+{% block beforeform%}
+ <div class="social-fb highlight corner-small clearfix">
+ <a href="{% url socialauth_login %}"
+ class="button">Con&eacute;ctate con Facebook</a>
+ </div>
+ <p class="login-help-text">
+ ...o crea una cuenta en webeando.me:
+ </p>
+{% endblock %}
+
+{% block class_form %}users-form-create{% endblock %}
+
+{% block button %}{% trans 'Crear' %}{% endblock %}
View
5 users/templates/form.users.settings.html
@@ -0,0 +1,5 @@
+{% extends 'forms/form.html' %}
+{% load i18n %}
+
+
+{% block extra_form %}enctype="multipart/form-data"{% endblock %}
View
25 users/templates/mail.password_instructions.html
@@ -0,0 +1,25 @@
+{% extends 'email/email_base_notification.html' %}
+
+{% block body %}
+<h1 style="font-size: 1.5em">Olvidaste tu contrase&ntilde;a?</h1>
+<p>
+ Hemos recibido una solicitud para cambiar la contrase&ntilde;a de tu cuenta:
+ {{ user.username }}<br/>
+ Si deseas cambiar tu contrase&ntilde;a por que la olvidaste,
+ haz click en el siguiente enlace (o copialo y pegalo en tu navegador):<br/>
+</p>
+<p style="padding: 0.5em 1em; background: #444; -moz-border-radius: 10px; border-radius: 10px;">
+ <a style="color: #FF8D01" href="{{ protocol }}://{{ domain }}{% url password_reset_confirm uid token %}">
+ {{ protocol }}://{{ domain }}{% url password_reset_confirm uid token %}
+ </a>
+</p>
+<p>
+ Si no quieres cambiar tu contrase&ntilde;a, ignora este mensaje.
+ Tu contrase&ntilde;a no ser&aacute; cambiada. Si tienes alguna duda
+ cont&aacute;ctanos.
+</p>
+<p>
+ Gracias,<br/>
+ <span style="color:#888">El equipo de {{ site_name }}</span>
+</p>
+{% endblock %}
View
18 users/templates/mail.password_instructions.txt
@@ -0,0 +1,18 @@
+{% extends 'email/email_base_notification.txt' %}
+
+{% block body %}
+Olvidaste tu contrase&ntilde;a?
+--------------------------
+Hemos recibido una solicitud para cambiar la contrase&ntilde;a de tu cuenta: {{ user.username }}
+Si deseas cambiar tu contrase&ntilde;a por que la olvidaste, copia y pega el
+siguiente enlace en tu navegador:
+
+{{ protocol }}://{{ domain }}{% url password_reset_confirm uid token %}
+
+Si no quieres cambiar tu contrase&ntilde;a, ignora este mensaje.
+Tu contrase&ntilde;a no ser&aacute; cambiada. Si tienes alguna duda
+cont&aacute;ctanos.
+
+Gracias,
+El equipo de {{ site_name }}
+{% endblock %}
View
42 users/templates/mail.welcome.html
@@ -0,0 +1,42 @@
+{% extends 'email/email_base_notification.html' %}
+
+{% block body %}
+<h1 style="font-size: 2em; color: #fff;">Hey! que tal?</h1>
+<p>
+ Gracias por formar parte de {{ SITE_NAME }} te compartimos algunos datos
+ importantes:
+</p>
+<p style="padding: 0.5em 1em; background: #444; -moz-border-radius: 10px; border-radius: 10px;">
+ <strong>Nombre de usuario</strong>:
+ {{ user.username }}<br/>
+ <strong>Perfil</strong>:
+ <a href="http://{{ user.username }}.{{ DOMAIN }}"
+ style="color: #FF8D01" >http://{{ user.username }}.{{ DOMAIN }}</a>
+</p>
+
+<h2 style="font-size: 1.5em; color: #fff;">Qu&eacute; es Webeando.me?</h2>
+<p>
+ Webeando.me es un comunidad de usuarios que gustan explorar y compartir
+ contenido entretenido en su tiempo libre... un sitio para webearse ;-)
+</p>
+
+<h2 style="font-size: 1.5em; color: #fff;">Cuales son las reglas?</h2>
+<ul>
+ <li><strong>Diviertete</strong>: Todo es por diversi&oacute;n. &Uacute;nete
+ y disfruta!</li>
+ <li><strong>Comparte</strong>: La mejor forma de entretenerse y divertirse
+ es compartiendo contenido con los demas. Comparte tu tambien.</li>
+ <li><strong>Respeta</strong>: Divertirse no significa burlarse de otras
+ personas. Respeta a los demas.</li>
+ <li><strong>Copyright</strong>: Comparte contenidos del que eres propietario
+ o adjunta el enlace de la fuente. Respeta los derechos de autor.</li>
+ <li><strong>No repitas</strong>: Si alguien ya comparti&oacute; un contenido, no
+ vuelvas a compartirlo nuevamente.</li>
+</ul>
+
+<p>
+ Gracias,<br/>
+ <span style="color:#888">El equipo de {{ SITE_NAME }}</span>
+</p>
+
+{% endblock %}
View
28 users/templates/mail.welcome.txt
@@ -0,0 +1,28 @@
+{% extends 'email/email_base_notification.txt' %}
+
+{% block body %}
+Hey! que tal?
+---------------------
+Gracias por formar parte de {{ SITE_NAME }} te compartimos algunos datos importantes:
+
+Nombre de usuario: {{user.username}}
+Perfil: http://{{ user.username }}.{{DOMAIN}}
+
+Qué; es webeando.me?
+----------------------
+Webeando.me es un comunidad de usuarios que gustan explorar y compartir
+contenido entretenido en su tiempo libre... un sitio para webearse ;-)
+
+
+Cuales son las reglas
+----------------------
+Diviertete: Todo es por diversión. Unete y disfruta!
+Comparte: La mejor forma de entretenerse y divertirse es compartiendo contenido con los demas. Comparte tu también.
+Respeta: Divertirse no significa burlarse de otras personas. Respeta a los demas.
+Copyright: Comparte contenidos del que eres propietario o adjunta el enlace de la fuente. Respeta los derechos de autor.
+No repitas: Si alguien ya compartió un contenido, no vuelvas a compartirlo nuevamente.
+
+Gracias,
+El equipo de {{ SITE_NAME }}
+
+{% endblock %}
View
15 users/templates/page.password_reset.complete.html
@@ -0,0 +1,15 @@
+{% extends "base/layout.html" %}
+{% load i18n base %}
+
+{% block bodyclass %}{{block.super}} small{% endblock %}
+{% block beforewrapper %}{% endblock %}
+{% block mainmenu %}{% endblock %}
+
+
+{% block content %}
+ <h1>{% trans 'Password reset complete' %}</h1>
+
+ <p>{% trans "Your password has been set. You may go ahead and log in now." %}</p>
+
+ <p><a href="{{ login_url }}">{% trans 'Log in' %}</a></p>
+{% endblock %}
View
35 users/templates/page.password_reset.confirm.html
@@ -0,0 +1,35 @@
+{% extends "base/layout.html" %}
+{% load i18n base %}
+
+{% block bodyclass %}{{block.super}} small{% endblock %}
+{% block beforewrapper %}{% endblock %}
+{% block mainmenu %}{% endblock %}
+
+{% block content %}
+ {% if validlink %}
+
+ <h1>{% trans 'Enter new password' %}</h1>
+
+ <p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
+
+ <form action="" method="post">
+ {% csrf_token %}
+ {{ form.new_password1.errors }}
+ <p class="aligned wide">
+ <label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}
+ </p>
+ {{ form.new_password2.errors }}
+ <p class="aligned wide">
+ <label for="id_new_password2">{% trans 'Confirm password:' %}</label>{{ form.new_password2 }}
+ </p>
+ <p><input type="submit" value="{% trans 'Change my password' %}" /></p>
+ </form>
+
+ {% else %}
+
+ <h1>{% trans 'Password reset unsuccessful' %}</h1>
+
+ <p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
+
+ {% endif %}
+{% endblock %}
View
12 users/templates/page.password_reset.done.html
@@ -0,0 +1,12 @@
+{% extends "base/layout.html" %}
+{% load i18n %}
+
+
+{% block bodyclass %}{{block.super}} small{% endblock %}
+{% block beforewrapper %}{% endblock %}
+{% block mainmenu %}{% endblock %}
+
+{% block content %}
+ <h1>{% trans 'La contraseña se modificó correctamente' %}</h1>
+ <p>{% trans "We've e-mailed you instructions for setting your password to the e-mail address you submitted. You should be receiving it shortly." %}</p>
+{% endblock %}
View
31 users/templates/page.password_reset.html
@@ -0,0 +1,31 @@
+{% extends "base/layout.html" %}
+{% load i18n %}
+
+{% block bodyclass %}{{block.super}} small{% endblock %}
+{% block beforewrapper %}{% endblock %}
+{% block mainmenu %}{% endblock %}
+
+{% block content %}
+ <h1>{% trans "Cambiar contraseña" %}</h1>
+
+ <p>
+ {% trans "Olvidaste tu contraseña? Ingresa tu e-mail abajo, y te enviaremos un mail con instrucciones para configurar uno nuevo." %}
+ </p>
+
+ <form action="" method="post">
+ <fieldset>
+ <legend>{% block legend %}{% endblock%}</legend>
+ {% csrf_token %}
+ {{ form.email.errors }}
+
+ <div class="field-wrapper {{field.name}} clearfix {% if field.errors %}invalid{% endif %}">
+ <label for="id_email">{% trans 'Tu e-mail:' %}
+ </label>
+ {{ form.email }}
+ </div>
+ <div class="field-wrapper clearfix submit">
+ <button type="submit">{% trans 'Reset my password' %}</button>
+ </div>
+ </fieldset>
+ </form>
+{% endblock %}
View
33 users/templates/page.users.index.html
@@ -0,0 +1,33 @@
+{% extends 'base/layout.html' %}
+{% load i18n base thumbnails_tags humanize %}
+
+
+{% block pagetitle %}{% trans 'Gente' %} - {{ block.super }}{% endblock %}
+
+
+{% block beforewrapper %}{% endblock %}
+
+
+{% block content %}
+<h1 class="page-title">{% trans 'Gente' %}</h1>
+<div class="clearfix">
+ <ul id="people" class="clearfix">
+ {% for user in object_list %}
+ {% with user_profile=profiles_dict|hash:user.id %}
+ <li class="user-object">
+ <a class="avatar span" href="{{ user_profile.get_absolute_url }}">
+ <img src='{% thumbnail_url user_profile "s" %}' />
+ </a>
+ <a class="name span last" href="{{ user_profile.get_absolute_url }}">
+ {{ user.first_name }}<br/>
+ {{ user.last_name }}
+ </a>
+ <span class="username span last">{{ user.username }}</span>
+ </li>
+ {% endwith %}
+ {% endfor %}
+ </ul>
+
+ {% include 'pagination/basic.html' %}
+</div>
+{% endblock %}
View
11 users/templates/page.users.login.html
@@ -0,0 +1,11 @@
+{% extends "base/layout.html" %}
+{% load i18n base %}
+
+{% block bodyclass %}{{block.super}} small{% endblock %}
+{% block beforewrapper %}{% endblock %}
+{% block mainmenu %}{% endblock %}
+
+
+{% block content %}
+ {% include 'form.users.login.html' %}
+{% endblock %}
View
39 users/templates/page.users.profile.html
@@ -0,0 +1,39 @@
+{% extends 'page.webeos.index.html' %}
+{% load i18n thumbnails_tags %}
+
+{% block headdescription %}Webeos, imagenes graciosas, fotos divertidas subidas por {{ object.first_name }} {{ object.last_name }} - {{ object.username }} - {{ SITE_NAME }}{% endblock %}
+
+{% block pagetitle %}{{ object.first_name }} ({{ object.username }}) - {{ block.super }}{% endblock %}
+{% block bodyclass %}{{block.super}} withmenu{% endblock %}
+
+
+{% block contenttitle %}{% endblock %}
+
+
+{% block beforecontentzone %}
+<div id="user-data" class="user-data clearfix corner">
+ <div class="avatar span">
+ <a href="{% url users_profile object.username %}">
+ <img src="{% thumbnail_url user_profile "m" %}"
+ alt="{{ object.username }}" />
+ </a>
+ </div>
+ <div class="user-info span last">
+ <h1 class="title">
+ {{ object.first_name}}
+ ({{ object.username }})
+ </h1>
+
+ {% if user_profile.description %}
+ <p class='description'>
+ {{ user_profile.description }}`
+ </p>
+ {% endif %}
+
+ {% if user_profile.url %}
+ <a href="{{ user_profile.url }}">{{ user_profile.url }}</a>
+ {% endif %}
+ </div>
+</div>
+{% endblock %}
+
View
11 users/templates/page.users.register.html
@@ -0,0 +1,11 @@
+{% extends 'base/layout.html' %}
+
+
+{% block bodyclass %}{{block.super}} small{% endblock %}
+{% block beforewrapper %}{% endblock %}
+{% block mainmenu %}{% endblock %}
+
+
+{% block content %}
+ {% include 'form.users.register.html' %}
+{% endblock %}
View
31 users/templates/page.users.settings.html
@@ -0,0 +1,31 @@
+{% extends 'base/layout.html' %}
+{% load i18n %}
+
+
+{% block bodyclass %}{{block.super}} small withmenu{% endblock %}
+
+
+{% block beforewrapper %}
+<div id="menubarwrapper" class="container corner">
+ <div id="menubar" class="menubar clearfix">
+ <ul class="menu">
+ <li class="{% if view_name == 'users-account' %}current{% endif %}">
+ <a href="{% url users_account %}">Cuenta</a>
+ </li>
+ <li class="{% if view_name == 'users-personal' %}current{% endif %}">
+ <a href="{% url users_personal %}">Personal</a>
+ </li>
+ <li class="{% if view_name == 'users-design' %}current{% endif %}">
+ <a href="{% url users_design %}">Dise&ntilde;o</a>
+ </li>
+ </ul>
+ </div>
+</div>
+{% endblock %}
+
+{% block mainmenu %}{% endblock %}
+
+
+{% block content %}
+ {% include 'form.users.settings.html' %}
+{% endblock %}
View
377 users/tests.py
@@ -0,0 +1,377 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mandla Web Studio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'Jose Maria Zambrana Arze'
+__email__ = 'contact@josezambrana.com'
+__version__ = '0.1'
+__copyright__ = 'Copyright 2012, Mandla Web Studio'
+
+
+import logging
+import re
+from os import path
+
+from django.conf import settings
+from django.core import mail
+from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
+
+from common.tests import TestBase
+from thumbnails.tests import IMAGE_TEST
+from users.models import Profile
+
+
+class TestUsersViews(TestBase):
+ fixtures = ['users']
+
+ def setUp(self):
+ TestBase.setUp(self)
+ self.data = {
+ "first_name": "Test",
+ "last_name": "Fake",
+ "username": "testfake",
+ "password1": "fakepass",
+ "password2": "fakepass",
+ "email": "test@fake.com",
+ }
+
+ self.model = User
+ self.object = self.model.objects.get(pk=1)
+
+ def test_users_login(self):
+ """
+ Como usuario debo ser capaz de ver el formulario de identificación
+ """
+ response = self.client_get('users_login')
+ assert response.status_code == 200
+
+ def test_valid_login(self):
+ """
+ Como usuario debo ser capaz de identificarme en el sitio.
+ """
+
+ response = self.client_post('users_login', data={'username': 'testaccount', 'password': 'fakepass'})
+ assert response.status_code == 302
+
+ def test_users_logout(self):
+ """
+ Como usuario debo ser capaz de salir de sitio.
+ """
+ self._login()
+ response = self.client_get('users_logout')
+ self.assertRedirects(response, reverse('home'), host=self.server_name)
+
+ def test_users_register(self):
+ """
+ Como visitante debo ser capaz de ver la página de registro del sitio.
+ """
+
+ response = self.client_get('users_register')
+ assert response.status_code == 200
+
+ def test_create_user(self):
+ """
+ Como visitante debo ser capaz de registrarme en el sitio.
+ """
+
+ mail_count = len(mail.outbox)
+
+ # Enviamos la petición para crear un usuario.
+ response = self.client_post('users_register', data=self.data)
+ assert response.status_code == 302
+
+ # Verificamos que se envió un mail de bienvenida.
+ assert (mail_count + 1) == len(mail.outbox)
+
+ # Verificamos que se crea un perfil
+ p = Profile.objects.get(user__username=self.data['username'])
+ assert p.username == self.data['username']
+
+ def test_invalid_username(self):
+ """
+ El sistema debe validar el nombre de usuario antes de crear el usuario.
+ """
+ self.invalid_usernames = ['', '!@#$%^username', 'my_username', 'my@username.com']
+
+ for username in self.invalid_usernames:
+ self.data['username'] = username
+ response = self.client_post('users_register', data=self.data)
+ assert response.status_code != 302
+
+ def test_invalid_email(self):
+ """
+ El sistema debe validar el email antes de crear el usuario.
+ """
+
+ self.invalid_emails = ['', 'invalidmail', 'invalid@mail']
+
+ for email in self.invalid_emails:
+ self.data['email'] = email
+ response = self.client_post('users_register', data=self.data)
+ assert response.status_code != 302
+
+ def test_invalid_firstname(self):
+ """
+ El sistema debe ser capaz de validar el primer nombre antes de
+ registrar el usuario
+ """
+
+ self.data['first_name'] = ''
+ response = self.client_post('users_register', data=self.data)
+ assert response.status_code != 302
+
+ def test_valid_usernames(self):
+ """
+ El sistema debe permitir registrar solo nombre usuario que contengan
+ caracteres alfanúmericos y el caracter -
+ """
+
+ self.validusernames = ['fakeuser', 'fake-user', 'fakeuser01']
+
+ for username in self.validusernames:
+ # Actualizamos los datos con el nombre de usuario valido
+ self.data['username'] = username
+ self.data['email'] = '%s@mail.com' % username
+
+ # Enviamos la petición para registrar y verificamos
+ response = self.client_post('users_register', data=self.data)
+ assert response.status_code == 302
+
+ # Sale del sistema para intentar con un nuevo nombre de usuario.
+ self.logout()
+
+ def test_profile_get(self):
+ """
+ Como usuario debo poder ingresar en los perfiles de los usuarios que se crean.
+ """
+ # Creamos los usuarios
+ self.test_valid_usernames()
+
+ #verificamos que todos se cargan correctamente.
+ for username in self.validusernames:
+ url = 'http://%s.%s' % (username, self.server_name)
+ response = self.client_get(url)
+ self.assertEquals(response.status_code, 200)
+
+ def test_account(self):
+ """
+ El sistema debe ser capaz de mostrar el formulario para modificar los
+ datos del usuario.
+ """
+
+ from django.contrib.auth import REDIRECT_FIELD_NAME
+
+ # Verificamos que el usuario tienen que estar logueado
+ response = self.client_get('users_account')
+ expected_url = '%s?%s=%s' % (reverse('users_login'),
+ REDIRECT_FIELD_NAME,
+ reverse('users_account'))
+ self.assertRedirects(response, expected_url, host=self.server_name)
+
+ # Verificamos con un usuario logueado.
+ self._login()
+ response = self.client_get('users_account')
+ logging.info('status_code: %s ' % response.status_code)
+ assert response.status_code == 200
+
+ # Modificamos los valores
+ data = {
+ 'first_name': 'new first name',
+ 'last_name': 'new second name',
+ 'email': 'new@email.com'
+ }
+ response = self.client_post('users_account', data=data)
+ assert response.status_code == 302
+
+ # Verificamos los valores cambiados
+ user = self._update(self.user)
+ assert user.first_name == data['first_name']
+ assert user.last_name == data['last_name']
+ assert user.email == data['email']
+
+ def test_change_account_without_change_password(self):
+ """
+ Como usuario debo ser capaz de cambiar mis datos de perfil sin cambiar
+ mi contraseña
+ """
+ data = self.data.copy()
+ del data["username"]
+ data['password1'] = ''
+ data['password2'] = ''
+
+ self.login("zero", "fakepass")
+ self.object = User.objects.get(username='zero')
+
+ response = self.client_post('users_account', data=data)
+ assert response.status_code == 302
+
+ self.client_get('users_logout')
+
+ response = self.client_post('users_login', data={'username': 'zero', 'password': 'fakepass'})
+ self.assertRedirects(response, reverse('home'), host=self.server_name)
+
+ def test_avatar_upload(self):
+ """
+ Como usuario debo ser capaz de subir un avatar a mi perfil.
+ """
+
+ self._login()
+ image = open(IMAGE_TEST)
+
+ data = {
+ 'image': image
+ }
+ response = self.client_post('users_personal', data=data)
+ assert response.status_code == 302
+
+ profile = self.user.get_profile()
+ for size in Profile.sizes.keys():
+ assert profile.thumbnail_exists(size)
+
+ def test_change_personal_data(self):
+ """
+ Como usuario debo ser capaz de añadir mi website y una descripción de
+ mi cuenta.
+ """
+
+ self._login()
+
+ # Verificamos que se muestra el formulario
+ response = self.client_get('users_personal')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, '<form')
+
+ data = {
+ 'description': 'soy una cuenta para hacer tests.',
+ 'url': 'http://tester.me/'
+ }
+ response = self.client_post('users_personal', data=data)
+ assert response.status_code == 302
+
+ profile = self.user.get_profile()
+ assert profile.description == data['description']
+ assert profile.url == data['url']
+
+ def test_password_reset(self):
+ """
+ Como usuario debo ser capaz de solicitar cambiar mi password.
+ """
+
+ # verificamos que la pagina para resetear el password
+ response = self.client_get('password_reset')
+ assert response.status_code == 200
+
+ # Verificamos la petición de cambiar el password y el envío de mail
+ mail_count = len(mail.outbox)
+
+ response = self.client_post('password_reset', data={'email': self.email})
+ assert response.status_code == 302
+ assert (mail_count + 1) == len(mail.outbox)
+
+ def test_reset_confirm(self):
+ """
+ Como usuario debo ser capaz de cambiar mi contraseña a travez de un
+ código.
+ """
+
+ self.test_password_reset()
+ url, path = self._read_signup_email(mail.outbox[0])
+
+ # verificamos que la pagina se carga correctamente.
+ response = self.client_get(path)
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue("Please enter your new password" in response.content)
+
+ # verificamos que se cambia de contraseña
+ data = {
+ 'new_password1': 'nuevopass',
+ 'new_password2': 'nuevopass',
+ }
+ response = self.client_post(path, data=data)
+ self.assertRedirects(response, reverse('password_reset_complete'), host=self.server_name)
+ user = User.objects.get(username=self.username)
+ self.assertTrue(user.check_password('nuevopass'))
+
+ def test_set_design(self):
+ """
+ Como usuario debo ser capaz de cambiar el color de fondo y la imagen
+ de mi perfil y el color de los enlaces.
+ """
+
+ self._login()
+ user = self.get_user()
+ profile = user.get_profile()
+
+ # Verificamos que se muestre el formulario de diseño
+ response = self.client_get('users_design')
+ self.assertEquals(response.status_code, 200)
+ self.assertContains(response, '<form')
+
+ # Cambiamos el color de fondo y de los links
+ data = {
+ "background_color": "#fff",
+ "links_color": "#888",
+ "button_background": '#444',
+ "button_color": '#aaa'
+ }
+
+ response = self.client_post('users_design', data=data)
+ assert response.status_code == 302
+
+ # Verificamos el cambio en el perfil.
+ profile = self._update(profile)
+ self.assertEquals(profile.background_color, data['background_color'])
+ self.assertEquals(profile.links_color, data['links_color'])
+
+ # Verificamos que solo permita colores válidos.
+ invalids = ['#fff9', '#a', 'asdf', '123456']
+
+ for field in data.keys():
+ for invalid in invalids:
+ data[field] = invalid
+ response = self.client_post('users_design', data=data)
+ assert response.status_code == 200
+
+ def test_set_background(self):
+ """
+ Como usuario debo ser capaz de colocar una imagen de fondo en mi perfil
+ """
+
+ self._login()
+
+ user = self.get_user()
+ profile = user.get_profile()
+ background_file = path.join(path.abspath(path.dirname(__file__)),
+ 'static', 'bg.jpg')
+ assert not profile.background
+
+ # Posteamos la imagen
+ data = {
+ "background": open(background_file)
+ }
+ response = self.client_post('users_design', data=data)
+ self.assertEquals(response.status_code, 302)
+
+ # Verificamos que se ha subido la imagen
+ profile = self._update(profile)
+ self.assertTrue(profile.background)
+
+ def _read_signup_email(self, email):
+ urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body)
+ self.assertTrue(urlmatch is not None, "No URL found in sent email")
+ return urlmatch.group(), urlmatch.groups()[0]
+
+
View
67 users/urls.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mandla Web Studio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'Jose Maria Zambrana Arze'
+__email__ = 'contact@josezambrana.com'
+__version__ = '0.1'
+__copyright__ = 'Copyright 2012, Mandla Web Studio'
+
+
+from django.conf.urls.defaults import *
+from django.contrib.auth.models import User
+
+from django.contrib.auth.views import login, logout
+
+from users.views import UsersIndex
+from users.views import UsersProfile
+from users.views import UsersUpdate
+from users.views import UsersUpdateProfile
+from users.views import UsersUpdateDesign
+
+
+login_kwargs = {
+ 'template_name': 'page.users.login.html',
+ 'current_app': 'users'
+}
+logout_kwargs = {
+ 'next_page': '/'
+}
+
+
+urlpatterns = patterns('',
+ # usuarios
+ url(r'^$', UsersIndex.as_view(), name='users_index'),
+ url(r'^login$', login, login_kwargs, name='users_login'),
+ url(r'^logout$', logout, logout_kwargs, name='users_logout'),
+)
+
+urlpatterns += patterns('users.views',
+ # password reset
+ url(r'^password/$', 'password_reset', name='password_reset'),
+ url(r'^password/completo$', 'password_reset_done', name='password_reset_done'),
+ url(r'^reset/(?P<uidb36>[0-9A-Za-z]{1,13})-(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
+ 'password_reset_confirm', name='password_reset_confirm'),
+ url(r'^reset/completo/$', 'password_reset_complete', name='password_reset_complete'),
+
+ # User
+ url(r'^register$', 'register', name='users_register'),
+ url(r'^cuenta$', UsersUpdate.as_view(), name='users_account'),
+ url(r'^personal$', UsersUpdateProfile.as_view(), name='users_personal'),
+ url(r'^diseno$', UsersUpdateDesign.as_view(), name='users_design'),
+ url(r'^profile$', 'profile', name='users_profile'),
+ url(r'^(?P<username>[\w\-]+)', 'profile', name='users_profile')
+)
+
View
62 users/utils.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mandla Web Studio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'Jose Maria Zambrana Arze'
+__email__ = 'contact@josezambrana.com'
+__version__ = '0.1'
+__copyright__ = 'Copyright 2012, Mandla Web Studio'
+
+
+def get_dict_by_related(query, related):
+ """
+ Retorna un diccionario con los ids de los objetos de query como claves y
+ como valores a sus respectivos objetos que estan nombrados como *related*.
+
+ Por ejemplo dado una lista de posts que cada uno tiene un atributo llamado
+ 'user' que referencia a un objeto Usuario, retorna un diccionario con los
+ ids de los posts como claves y sus respectivos usuarios como valores::
+
+ {
+ <id_post>: <user>
+ ...
+ }
+
+ """
+
+ object_dict = {}
+
+ for obj in query:
+ field = getattr(obj, related)
+ key = str(field)
+ object_dict[key] = obj
+
+ return object_dict
+
+def get_dict_by_ids(model, ids):
+ """
+ Retorna un diccionario con los *ids* pasados como parametro y los objetos
+ que pertenecen al modelo *model* y que tienen sus id dentro de *ids*
+ """
+
+ objects_dict = {}
+
+ query = model.objects.filter(pk__in=ids)
+
+ for obj in query:
+ key = str(obj.id)
+ objects_dict[key] = obj
+
+ return objects_dict
View
295 users/views.py
@@ -0,0 +1,295 @@
+# -*- coding: utf-8 -*-
+# Copyright 2012 Mandla Web Studio
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__author__ = 'Jose Maria Zambrana Arze'
+__email__ = 'contact@josezambrana.com'
+__version__ = '0.1'
+__copyright__ = 'Copyright 2012, Mandla Web Studio'
+
+
+import logging
+import traceback
+
+from django import template
+from django.http import Http404
+from django.shortcuts import render_to_response
+from django.shortcuts import redirect
+from django.shortcuts import get_object_or_404
+
+from django.core.urlresolvers import reverse
+from django.conf import settings
+from django.contrib import messages
+
+from django.contrib.auth import authenticate, login as auth_login
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.views import login as login_view
+from django.contrib.auth.views import logout as logout_view
+
+from django.contrib.auth.models import User
+
+from django.contrib.auth.views import password_reset as django_reset
+from django.contrib.auth.views import password_reset_done as django_reset_done
+from django.contrib.auth.views import password_reset_confirm as django_reset_confirm
+from django.contrib.auth.views import password_reset_complete as django_reset_complete
+
+from django.contrib.sites.models import Site
+from django.utils.translation import ugettext_lazy as _
+
+from django.views.generic.list import ListView
+
+from common.views import UpdateView
+from common.views import LoginRequiredMixin
+from common.views import ListView
+from common.views import DetailView
+from common.views import OwnerRequiredMixin
+
+from users.forms import PasswordResetForm
+from users.forms import RegisterForm
+from users.forms import UserForm
+from users.forms import ProfileForm
+from users.forms import DesignForm
+
+from users.utils import get_dict_by_ids
+from users.models import Profile
+
+
+current_site = Site.objects.get_current()
+
+
+def register(request, **kwargs):
+ if request.user.is_authenticated():
+ messages.error(request, 'Ya estas registrado')
+ logging.error('Ya estas registrado: %s ' % request.user.username)
+ return redirect(reverse('error'))
+
+ form = RegisterForm()
+
+ if request.method == 'POST':
+ form = RegisterForm(request.POST)
+
+ if form.is_valid():
+ user = form.save()
+ messages.success(request, _("Bienvenido a %s" % current_site.name))
+
+ user = authenticate(username=form.cleaned_data['username'],
+ password=form.cleaned_data['password1'])
+ auth_login(request, user)
+
+ return redirect(reverse('home'))
+
+ context = {
+ 'form': form
+ }
+
+ c = template.RequestContext(request, context)
+ return render_to_response('page.users.register.html', c)
+
+
+# password reset
+def password_reset(request):
+ kwargs = {
+ 'template_name': 'page.password_reset.html',
+ 'post_reset_redirect': reverse('password_reset_done'),
+ 'password_reset_form': PasswordResetForm
+ }
+ return django_reset(request, **kwargs)
+
+
+def password_reset_done(request):
+ kwargs = {
+ 'template_name': 'page.password_reset.done.html'
+ }
+ return django_reset_done(request, **kwargs)
+
+
+def password_reset_confirm(request, uidb36=None, token=None):
+ kwargs = {
+ 'uidb36': uidb36,
+ 'token': token,
+ 'template_name': 'page.password_reset.confirm.html',
+ 'post_reset_redirect': reverse('password_reset_complete')
+ }
+ return django_reset_confirm(request, **kwargs)
+
+
+def password_reset_complete(request):
+ kwargs = {
+ 'template_name': 'page.password_reset.complete.html'
+ }
+ return django_reset_complete(request, **kwargs)
+
+
+class AttachActors(object):
+ """
+ Añade la lista de actores que intervienen en la vista al contexto
+ """
+
+ def get_actors_ids(self, context):
+ """
+ Retorna los ids de todos los actores que intervienen en esta vista.
+ """
+
+ raise NotImplementedError
+
+ def get_context_data(self, **kwargs):
+ """
+ Retorna el contexto con el diccionario de usuarios y perfiles.
+ """
+ context = super(AttachActors, self).get_context_data(**kwargs)
+
+ # Añadimos al contexto los perfiles de los actores en un diccionario.
+ users_ids = self.get_actors_ids(context)
+ context['users_dict'] = get_dict_by_ids(User, users_ids)
+ context['profiles_dict'] = Profile.objects.profiles_dict(users_ids)
+
+ return context
+
+
+class UsersIndex(AttachActors, ListView):
+ """
+ Muestra los usuarios registrados en el sistema.
+ """
+
+ model = User
+ view_name = 'users-index'
+ app_name = 'users'
+
+ templates = {
+ 'html': 'page.users.index.html'
+ }
+
+ def get_actors_ids(self, context):
+ """
+ Retorna los ids de los usuario de la vista.
+ """
+
+ return [user.id for user in context['object_list']]
+
+
+class UsersUpdate(OwnerRequiredMixin, UpdateView):
+ """
+ Vista para actualizar los datos de la cuenta del usuario.
+ """
+
+ model = User
+ form_class = UserForm
+
+ view_name = 'users-account'
+ app_name = 'users'
+ templates = {
+ 'html': 'page.users.settings.html'
+ }
+
+ def get_object(self):
+ return self.request.user
+
+ def is_owner(self, user, current_user):
+ return user.id == current_user.id
+
+ def form_valid(self, form):
+ user = form.save()
+
+ self.success_message = _(u'Tu cuenta fue actualizada.')
+
+ return super(UsersUpdate, self).form_valid(form)
+
+ def get_success_redirect_url(self):
+ return reverse('users_profile', args=[self.request.user.username])
+
+
+class UsersUpdateProfile(OwnerRequiredMixin, UpdateView):
+ """
+ Vista para actualizar el perfil del usuario.
+ """
+
+ model = Profile
+ form_class = ProfileForm
+
+ view_name = 'users-personal'
+ app_name = 'users'
+ templates = {
+ 'html': 'page.users.settings.html'
+ }
+
+ def get_object(self):
+ profile = self.request.user.get_profile()
+ return profile
+
+ def form_valid(self, form):
+ profile = form.save()
+ self.success_message = _(u'Tus datos fueron actualizados.')
+ return super(UsersUpdateProfile, self).form_valid(form)
+
+ def get_success_redirect_url(self):
+ return reverse('users_profile', args=[self.request.user.username])
+
+
+class UsersUpdateDesign(UsersUpdateProfile):
+ """
+ Vista para actualizar el diseño del perfil del usuario.
+ """
+ form_class = DesignForm
+
+ view_name = 'users-design'
+
+ def form_valid(self, form):
+ """
+ Procesa el formulario.
+ """
+ profile = form.save()
+ self.success_message = _(u'El diseño fue actualizado correctamente')
+ return super(UsersUpdateDesign, self).form_valid(form)
+
+
+def profile(request, username=None):
+ """
+ Redirecciona la página de usuario con el subdominio: http://<username>.<domain>
+ """
+
+ if username is None:
+ user = request.user
+ else:
+ user = get_object_or_404(User, username=username)
+
+ user_url = 'http://%s.%s' % (user.username, current_site.domain)
+
+ return redirect(user_url)
+
+
+class UsersProfile(DetailView):
+ """
+ Muestra el perfil del usuario.
+ """
+
+ model = User
+
+ view_name = 'users-profile'
+ app_name = 'users'
+
+ templates = {
+ 'html': 'page.users.profile.html'
+ }
+
+ def get_object(self):
+ username = self.kwargs.get('username', self.request.user.username)
+ return get_object_or_404(User, username=username)
+
+ def get_context_data(self, **kwargs):
+ context = super(UsersProfile, self).get_context_data(**kwargs)
+ context['user_profile'] = context['object'].get_profile()
+ return context
+
+
Please sign in to comment.
Something went wrong with that request. Please try again.