A general project refactor

Removed pins django app, and moved code to the core. Moved user related
code out of core to the users app.
This commit is contained in:
Krzysztof Klimonda
2013-03-03 04:47:34 -08:00
parent cf86da266a
commit 53f05dbb6d
25 changed files with 196 additions and 298 deletions

View File

@@ -4,8 +4,8 @@ from tastypie.exceptions import Unauthorized
from tastypie.resources import ModelResource from tastypie.resources import ModelResource
from django_images.models import Thumbnail from django_images.models import Thumbnail
from pinry.core.models import User from .models import Pin, Image
from pinry.pins.models import Image, Pin from ..users.models import User
class PinryAuthorization(DjangoAuthorization): class PinryAuthorization(DjangoAuthorization):

View File

@@ -1,7 +1,7 @@
from django.core.validators import email_re from django.core.validators import email_re
from pinry.core.models import User from pinry.core.models import Pin
from pinry.pins.models import Pin from pinry.users.models import User
class CombinedAuthBackend(object): class CombinedAuthBackend(object):

View File

@@ -1,38 +1,18 @@
from django import forms from django import forms
from django.utils.translation import ugettext, ugettext_lazy as _
from django.contrib.auth.models import User from django_images.models import Image
class UserCreationForm(forms.ModelForm): FIELD_NAME_MAPPING = {
""" 'image': 'qqfile',
A form that creates a user, with no privileges, from the given username, }
email, and password.
"""
error_messages = { class ImageForm(forms.ModelForm):
'duplicate_username': _("A user with that username already exists."), def add_prefix(self, field_name):
} field_name = FIELD_NAME_MAPPING.get(field_name, field_name)
username = forms.RegexField(label=_("Username"), max_length=30, return super(ImageForm, self).add_prefix(field_name)
regex=r'^[\w-]+$')
password = forms.CharField(label=_("Password"),
widget=forms.PasswordInput)
class Meta: class Meta:
model = User model = Image
fields = ("username", "email") fields = ('image',)
def clean_username(self):
# Since User.username is unique, this check is redundant,
# but it sets a nicer error message than the ORM. See #13147.
username = self.cleaned_data["username"]
try:
User._default_manager.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError(self.error_messages['duplicate_username'])
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user

View File

@@ -1,10 +1,39 @@
import hashlib import urllib2
from django.contrib.auth.models import User as BaseUser from cStringIO import StringIO
class User(BaseUser): from django.core.files.uploadedfile import InMemoryUploadedFile
@property from django.db import models
def gravatar(self):
return hashlib.md5(self.email).hexdigest() from django_images.models import Image as BaseImage
from taggit.managers import TaggableManager
from ..users.models import User
class ImageManager(models.Manager):
# FIXME: Move this into an asynchronous task
def create_for_url(self, url):
file_name = url.split("/")[-1]
buf = StringIO()
buf.write(urllib2.urlopen(url).read())
obj = InMemoryUploadedFile(buf, 'image', file_name, None, buf.tell(), None)
return Image.objects.create(image=obj)
class Image(BaseImage):
objects = ImageManager()
class Meta: class Meta:
proxy = True proxy = True
class Pin(models.Model):
submitter = models.ForeignKey(User)
url = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
image = models.ForeignKey(Image, related_name='pin')
published = models.DateTimeField(auto_now_add=True)
tags = TaggableManager()
def __unicode__(self):
return self.url

View File

@@ -4,11 +4,11 @@ from django.conf import settings
from django.test.client import Client from django.test.client import Client
from django_images.models import Thumbnail from django_images.models import Thumbnail
from taggit.models import Tag from taggit.models import Tag
from tastypie.test import ResourceTestCase from tastypie.test import ResourceTestCase
from ..pins.models import User, Pin, Image from .models import Pin, Image
from ..users.models import User
def filter_generator_for(size): def filter_generator_for(size):

View File

@@ -1,9 +1,10 @@
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, include, url
from django.views.generic import TemplateView
from tastypie.api import Api from tastypie.api import Api
from .api import ImageResource, ThumbnailResource, PinResource, UserResource from .api import ImageResource, ThumbnailResource, PinResource, UserResource
from .views import CreateUser from .views import CreateImage
v1_api = Api(api_name='v1') v1_api = Api(api_name='v1')
@@ -13,18 +14,15 @@ v1_api.register(PinResource())
v1_api.register(UserResource()) v1_api.register(UserResource())
urlpatterns = patterns('',
)
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^api/', include(v1_api.urls, namespace='api')), url(r'^api/', include(v1_api.urls, namespace='api')),
url(r'^$', 'pinry.core.views.home', name='home'), url(r'^pin-form/$', TemplateView.as_view(template_name='core/pin_form.html'),
name='pin-form'),
url(r'^create-image/$', CreateImage.as_view(), name='create-image'),
url(r'^tag/(?P<tag>(\w|-)+)/$', TemplateView.as_view(template_name='core/pins.html'),
name='tag-pins'),
url(r'^private/$', 'pinry.core.views.private', name='private'), url(r'^private/$', 'pinry.core.views.private', name='private'),
url(r'^login/$', 'django.contrib.auth.views.login', url(r'^$', TemplateView.as_view(template_name='core/pins.html'),
{'template_name': 'user/login.html'}, name='login'), name='recent-pins'),
url(r'^logout/$', 'pinry.core.views.logout_user', name='logout'),
url(r'^register/$', CreateUser.as_view(), name='register'),
) )

View File

@@ -1,53 +1,35 @@
from django.contrib.auth.models import Permission
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from django.contrib.auth import logout, authenticate, login
from django.contrib import messages
from django.conf import settings
from django.utils.functional import lazy
from django.views.generic import CreateView from django.views.generic import CreateView
from django_images.models import Image
from .forms import UserCreationForm from braces.views import JSONResponseMixin, LoginRequiredMixin
from .models import User
from .forms import ImageForm
reverse_lazy = lambda name=None, *args : lazy(reverse, str)(name, args=args)
def home(request):
return HttpResponseRedirect(reverse('pins:recent-pins'))
def private(request): def private(request):
return TemplateResponse(request, 'user/private.html', None) return TemplateResponse(request, 'user/private.html', None)
class CreateUser(CreateView): class CreateImage(JSONResponseMixin, LoginRequiredMixin, CreateView):
template_name = 'user/register.html' template_name = None # JavaScript-only view
model = User model = Image
form_class = UserCreationForm form_class = ImageForm
success_url = reverse_lazy('pins:recent-pins')
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if not settings.ALLOW_NEW_REGISTRATIONS: if not request.is_ajax():
messages.error(request, "The admin of this service is not allowing new registrations.") return HttpResponseRedirect(reverse('core:recent-pins'))
return HttpResponseRedirect(reverse('pins:recent-pins')) super(CreateImage, self).get(request, *args, **kwargs)
return super(CreateUser, self).get(request, *args, **kwargs)
def form_valid(self, form): def form_valid(self, form):
redirect = super(CreateUser, self).form_valid(form) image = form.save()
permissions = Permission.objects.filter(codename__in=['add_pin', 'add_image']) return self.render_json_response({
user = authenticate(username=form.cleaned_data['username'], 'success': {
password=form.cleaned_data['password']) 'id': image.id
user.user_permissions = permissions }
login(self.request, user) })
return redirect
def form_invalid(self, form):
@login_required return self.render_json_response({'error': form.errors})
def logout_user(request):
logout(request)
messages.success(request, 'You have successfully logged out.')
return HttpResponseRedirect(reverse('core:home'))

View File

View File

@@ -1,19 +0,0 @@
from django import forms
from django_images.models import Image
FIELD_NAME_MAPPING = {
'image': 'qqfile',
}
class ImageForm(forms.ModelForm):
def add_prefix(self, field_name):
field_name = FIELD_NAME_MAPPING.get(field_name, field_name)
return super(ImageForm, self).add_prefix(field_name)
class Meta:
model = Image
fields = ('image',)

View File

@@ -1,39 +0,0 @@
import urllib2
from cStringIO import StringIO
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db import models
from django_images.models import Image as BaseImage
from taggit.managers import TaggableManager
from ..core.models import User
class ImageManager(models.Manager):
# FIXME: Move this into an asynchronous task
def create_for_url(self, url):
file_name = url.split("/")[-1]
buf = StringIO()
buf.write(urllib2.urlopen(url).read())
obj = InMemoryUploadedFile(buf, 'image', file_name, None, buf.tell(), None)
return Image.objects.create(image=obj)
class Image(BaseImage):
objects = ImageManager()
class Meta:
proxy = True
class Pin(models.Model):
submitter = models.ForeignKey(User)
url = models.TextField(blank=True, null=True)
description = models.TextField(blank=True, null=True)
image = models.ForeignKey(Image, related_name='pin')
published = models.DateTimeField(auto_now_add=True)
tags = TaggableManager()
def __unicode__(self):
return self.url

View File

@@ -1,37 +0,0 @@
from django.core.urlresolvers import reverse
from django.test import TestCase
from pinry.core.models import User
class CreateImageTest(TestCase):
fixtures = ['test_resources.json']
def setUp(self):
self.client.login(username='jdoe', password='password')
def test_form_post_unauthenticated(self):
post_data = {
'image': 'foobar.jpg'
}
self.client.logout()
response = self.client.post(reverse('pins:new-pin'), data=post_data)
expected_url = '{login_url}?next={next_url}'.format(**{
'login_url': reverse('core:login'),
'next_url': reverse('pins:new-pin')
})
self.assertRedirects(response, expected_url=expected_url)
def test_form_post_browser(self):
post_data = {
'image': 'foobar.jpg'
}
response = self.client.post(reverse('pins:new-pin'), data=post_data)
self.assertEqual(response.status_code, 200)
def test_form_post_ajax(self):
post_data = {
'image': 'foobar.jpg'
}
response = self.client.post(reverse('pins:new-pin'), data=post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)

View File

@@ -1,15 +0,0 @@
from django.conf.urls import patterns, url
from django.views.generic import TemplateView
from .views import CreateImage
urlpatterns = patterns('pinry.pins.views',
url(r'^pin-form/$', TemplateView.as_view(template_name='core/pin_form.html'),
name='pin-form'),
url(r'^create-image/$', CreateImage.as_view(), name='create-image'),
url(r'^tag/(?P<tag>(\w|-)+)/$', TemplateView.as_view(template_name='core/pins.html'),
name='tag-pins'),
url(r'^$', TemplateView.as_view(template_name='core/pins.html'),
name='recent-pins'),
)

View File

@@ -1,56 +0,0 @@
import PIL
import mimetypes
mimetypes.init()
# this neat function is based on django-images and easy-thumbnails
def scale_and_crop(image, size, crop=False, upscale=False, quality=None):
# Open image and store format/metadata.
image.open()
im = PIL.Image.open(image)
im_format, im_info = im.format, im.info
if quality:
im_info['quality'] = quality
# Force PIL to load image data.
im.load()
source_x, source_y = [float(v) for v in im.size]
target_x, target_y = [float(v) for v in size]
if crop or not target_x or not target_y:
scale = max(target_x / source_x, target_y / source_y)
else:
scale = min(target_x / source_x, target_y / source_y)
# Handle one-dimensional targets.
if not target_x:
target_x = source_x * scale
elif not target_y:
target_y = source_y * scale
if scale < 1.0 or (scale > 1.0 and upscale):
im = im.resize((int(source_x * scale), int(source_y * scale)),
resample=PIL.Image.ANTIALIAS)
if crop:
# Use integer values now.
source_x, source_y = im.size
# Difference between new image size and requested size.
diff_x = int(source_x - min(source_x, target_x))
diff_y = int(source_y - min(source_y, target_y))
if diff_x or diff_y:
# Center cropping (default).
halfdiff_x, halfdiff_y = diff_x // 2, diff_y // 2
box = [halfdiff_x, halfdiff_y,
min(source_x, int(target_x) + halfdiff_x),
min(source_y, int(target_y) + halfdiff_y)]
# Finally, crop the image!
im = im.crop(box)
# Close image and replace format/metadata, as PIL blows this away.
im.format, im.info = im_format, im_info
image.close()
return im

View File

@@ -1,30 +0,0 @@
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.views.generic import CreateView
from braces.views import LoginRequiredMixin, JSONResponseMixin
from django_images.models import Image
from .forms import ImageForm
class CreateImage(JSONResponseMixin, LoginRequiredMixin, CreateView):
template_name = None # JavaScript-only view
model = Image
form_class = ImageForm
def get(self, request, *args, **kwargs):
if not request.is_ajax():
return HttpResponseRedirect(reverse('pins:recent-pins'))
super(CreateImage, self).get(request, *args, **kwargs)
def form_valid(self, form):
image = form.save()
return self.render_json_response({
'success': {
'id': image.id
}
})
def form_invalid(self, form):
return self.render_json_response({'error': form.errors})

View File

@@ -85,7 +85,7 @@ INSTALLED_APPS = (
'taggit', 'taggit',
'django_images', 'django_images',
'pinry.core', 'pinry.core',
'pinry.pins', 'pinry.users',
) )
IMAGE_SIZES = { IMAGE_SIZES = {

View File

@@ -44,15 +44,15 @@
<!-- Navigation --> <!-- Navigation -->
<div class="navbar navbar-fixed-top"> <div class="navbar navbar-fixed-top">
<div class="navbar-inner"> <div class="navbar-inner">
<a href="{% url 'core:home' %}" class="brand pull-left">{{ SITE_NAME }}</a> <a href="{% url 'core:recent-pins' %}" class="brand pull-left">{{ SITE_NAME }}</a>
<ul class="nav pull-right"> <ul class="nav pull-right">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li>{% include "includes/bookmarklet_link.html" %}</li> <li>{% include "includes/bookmarklet_link.html" %}</li>
<li><a onclick="pinForm()">New Pin</a></li> <li><a onclick="pinForm()">New Pin</a></li>
<li><a href="{% url 'core:logout' %}">Logout</a></li> <li><a href="{% url 'users:logout' %}">Logout</a></li>
{% else %} {% else %}
<li><a href="{% url 'core:login' %}">Login</a></li> <li><a href="{% url 'users:login' %}">Login</a></li>
<li><a href="{% url 'core:register' %}">Register</a></li> <li><a href="{% url 'users:register' %}">Register</a></li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>

View File

@@ -7,7 +7,7 @@
<div class="row"> <div class="row">
<div id="form" class="span6 offset3"> <div id="form" class="span6 offset3">
<h1>Login</h1> <h1>Login</h1>
<form action="{% url "core:login" %}" method="post" class="form-horizontal"> <form action="{% url "users:login" %}" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
{% for field in form %} {% for field in form %}
{% include "includes/field.html" %} {% include "includes/field.html" %}

View File

@@ -7,7 +7,7 @@
<div class="row"> <div class="row">
<div id="form" class="span6 offset3"> <div id="form" class="span6 offset3">
<h1>Register</h1> <h1>Register</h1>
<form action="{% url "core:register" %}" method="post" class="form-horizontal"> <form action="{% url "users:register" %}" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
{% for field in form %} {% for field in form %}
{% include "includes/field.html" %} {% include "includes/field.html" %}

View File

@@ -4,6 +4,6 @@ from django.conf import settings
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^pins/', include('pinry.pins.urls', namespace='pins')), url(r'', include('pinry.core.urls', namespace='core')),
url(r'', include('pinry.core.urls', namespace='core')), url(r'', include('pinry.users.urls', namespace='users')),
) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

1
pinry/users/__init__.py Normal file
View File

@@ -0,0 +1 @@

38
pinry/users/forms.py Normal file
View File

@@ -0,0 +1,38 @@
from django import forms
from django.utils.translation import ugettext, ugettext_lazy as _
from django.contrib.auth.models import User
class UserCreationForm(forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given username,
email, and password.
"""
error_messages = {
'duplicate_username': _("A user with that username already exists."),
}
username = forms.RegexField(label=_("Username"), max_length=30,
regex=r'^[\w-]+$')
password = forms.CharField(label=_("Password"),
widget=forms.PasswordInput)
class Meta:
model = User
fields = ("username", "email")
def clean_username(self):
# Since User.username is unique, this check is redundant,
# but it sets a nicer error message than the ORM. See #13147.
username = self.cleaned_data["username"]
try:
User._default_manager.get(username=username)
except User.DoesNotExist:
return username
raise forms.ValidationError(self.error_messages['duplicate_username'])
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password"])
if commit:
user.save()
return user

12
pinry/users/models.py Normal file
View File

@@ -0,0 +1,12 @@
import hashlib
from django.contrib.auth.models import User as BaseUser
class User(BaseUser):
@property
def gravatar(self):
return hashlib.md5(self.email).hexdigest()
class Meta:
proxy = True

10
pinry/users/urls.py Normal file
View File

@@ -0,0 +1,10 @@
from django.conf.urls import patterns, url
from .views import CreateUser
urlpatterns = patterns('',
url(r'^login/$', 'django.contrib.auth.views.login',
{'template_name': 'user/login.html'}, name='login'),
url(r'^logout/$', 'pinry.users.views.logout_user', name='logout'),
url(r'^register/$', CreateUser.as_view(), name='register'),
)

44
pinry/users/views.py Normal file
View File

@@ -0,0 +1,44 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import Permission
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.utils.functional import lazy
from django.views.generic import CreateView
from .forms import UserCreationForm
from pinry.users.models import User
reverse_lazy = lambda name=None, *args: lazy(reverse, str)(name, args=args)
class CreateUser(CreateView):
template_name = 'user/register.html'
model = User
form_class = UserCreationForm
success_url = reverse_lazy('pins:recent-pins')
def get(self, request, *args, **kwargs):
if not settings.ALLOW_NEW_REGISTRATIONS:
messages.error(request, "The admin of this service is not allowing new registrations.")
return HttpResponseRedirect(reverse('pins:recent-pins'))
return super(CreateUser, self).get(request, *args, **kwargs)
def form_valid(self, form):
redirect = super(CreateUser, self).form_valid(form)
permissions = Permission.objects.filter(codename__in=['add_pin', 'add_image'])
user = authenticate(username=form.cleaned_data['username'],
password=form.cleaned_data['password'])
user.user_permissions = permissions
login(self.request, user)
return redirect
@login_required
def logout_user(request):
logout(request)
messages.success(request, 'You have successfully logged out.')
return HttpResponseRedirect(reverse('core:home'))