Removed pins django app, and moved code to the core. Moved user related code out of core to the users app.tags/v1.0.0
@@ -4,8 +4,8 @@ from tastypie.exceptions import Unauthorized | |||
from tastypie.resources import ModelResource | |||
from django_images.models import Thumbnail | |||
from pinry.core.models import User | |||
from pinry.pins.models import Image, Pin | |||
from .models import Pin, Image | |||
from ..users.models import User | |||
class PinryAuthorization(DjangoAuthorization): | |||
@@ -1,7 +1,7 @@ | |||
from django.core.validators import email_re | |||
from pinry.core.models import User | |||
from pinry.pins.models import Pin | |||
from pinry.core.models import Pin | |||
from pinry.users.models import User | |||
class CombinedAuthBackend(object): | |||
@@ -1,38 +1,18 @@ | |||
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): | |||
""" | |||
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") | |||
FIELD_NAME_MAPPING = { | |||
'image': 'qqfile', | |||
} | |||
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']) | |||
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) | |||
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 | |||
class Meta: | |||
model = Image | |||
fields = ('image',) |
@@ -1,10 +1,39 @@ | |||
import hashlib | |||
from django.contrib.auth.models import User as BaseUser | |||
import urllib2 | |||
from cStringIO import StringIO | |||
class User(BaseUser): | |||
@property | |||
def gravatar(self): | |||
return hashlib.md5(self.email).hexdigest() | |||
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 ..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: | |||
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 |
@@ -4,11 +4,11 @@ from django.conf import settings | |||
from django.test.client import Client | |||
from django_images.models import Thumbnail | |||
from taggit.models import Tag | |||
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): | |||
@@ -1,9 +1,10 @@ | |||
from django.conf.urls import patterns, include, url | |||
from django.views.generic import TemplateView | |||
from tastypie.api import Api | |||
from .api import ImageResource, ThumbnailResource, PinResource, UserResource | |||
from .views import CreateUser | |||
from .views import CreateImage | |||
v1_api = Api(api_name='v1') | |||
@@ -14,17 +15,14 @@ v1_api.register(UserResource()) | |||
urlpatterns = patterns('', | |||
) | |||
urlpatterns = patterns('', | |||
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'^login/$', 'django.contrib.auth.views.login', | |||
{'template_name': 'user/login.html'}, name='login'), | |||
url(r'^logout/$', 'pinry.core.views.logout_user', name='logout'), | |||
url(r'^register/$', CreateUser.as_view(), name='register'), | |||
url(r'^$', TemplateView.as_view(template_name='core/pins.html'), | |||
name='recent-pins'), | |||
) |
@@ -1,53 +1,35 @@ | |||
from django.contrib.auth.models import Permission | |||
from django.template.response import TemplateResponse | |||
from django.http import HttpResponseRedirect | |||
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_images.models import Image | |||
from .forms import UserCreationForm | |||
from .models import User | |||
from braces.views import JSONResponseMixin, LoginRequiredMixin | |||
reverse_lazy = lambda name=None, *args : lazy(reverse, str)(name, args=args) | |||
def home(request): | |||
return HttpResponseRedirect(reverse('pins:recent-pins')) | |||
from .forms import ImageForm | |||
def private(request): | |||
return TemplateResponse(request, 'user/private.html', None) | |||
class CreateUser(CreateView): | |||
template_name = 'user/register.html' | |||
model = User | |||
form_class = UserCreationForm | |||
success_url = reverse_lazy('pins:recent-pins') | |||
class CreateImage(JSONResponseMixin, LoginRequiredMixin, CreateView): | |||
template_name = None # JavaScript-only view | |||
model = Image | |||
form_class = ImageForm | |||
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) | |||
if not request.is_ajax(): | |||
return HttpResponseRedirect(reverse('core:recent-pins')) | |||
super(CreateImage, 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')) | |||
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}) |
@@ -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',) |
@@ -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 |
@@ -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) |
@@ -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'), | |||
) |
@@ -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 | |||
@@ -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}) |
@@ -85,7 +85,7 @@ INSTALLED_APPS = ( | |||
'taggit', | |||
'django_images', | |||
'pinry.core', | |||
'pinry.pins', | |||
'pinry.users', | |||
) | |||
IMAGE_SIZES = { | |||
@@ -44,15 +44,15 @@ | |||
<!-- Navigation --> | |||
<div class="navbar navbar-fixed-top"> | |||
<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"> | |||
{% if user.is_authenticated %} | |||
<li>{% include "includes/bookmarklet_link.html" %}</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 %} | |||
<li><a href="{% url 'core:login' %}">Login</a></li> | |||
<li><a href="{% url 'core:register' %}">Register</a></li> | |||
<li><a href="{% url 'users:login' %}">Login</a></li> | |||
<li><a href="{% url 'users:register' %}">Register</a></li> | |||
{% endif %} | |||
</ul> | |||
</div> | |||
@@ -7,7 +7,7 @@ | |||
<div class="row"> | |||
<div id="form" class="span6 offset3"> | |||
<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 %} | |||
{% for field in form %} | |||
{% include "includes/field.html" %} | |||
@@ -7,7 +7,7 @@ | |||
<div class="row"> | |||
<div id="form" class="span6 offset3"> | |||
<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 %} | |||
{% for field in form %} | |||
{% include "includes/field.html" %} | |||
@@ -4,6 +4,6 @@ from django.conf import settings | |||
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) |
@@ -0,0 +1 @@ | |||
@@ -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 |
@@ -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 |
@@ -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'), | |||
) |
@@ -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')) |