diff --git a/README.md b/README.md index 3ed38cc..b05ed6f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ shelfzilla -========== +=========== ## Prepare environment for local development @@ -21,3 +21,4 @@ fab runserver - grunt-cli installed as global resource - bower installed as a global resource +Enjoy! diff --git a/config/production/requirements.txt b/config/production/requirements.txt index 500be57..e1d6fd8 100644 --- a/config/production/requirements.txt +++ b/config/production/requirements.txt @@ -1,3 +1,4 @@ -r ../requirements.txt gunicorn==18.0 -toml==0.8.2 +toml==0.9.0 +opbeat==2.1 diff --git a/config/requirements.txt b/config/requirements.txt index 9cdc129..73bf50a 100644 --- a/config/requirements.txt +++ b/config/requirements.txt @@ -1,5 +1,5 @@ # Base -Django==1.7.1 +Django==1.7.2 # Admin django-suit==0.2.11 @@ -9,6 +9,9 @@ django-import-export==0.2.6 # Fixes +# Mailing +django-mailgun==0.2.2 + # Statics django-compressor==1.4 @@ -17,7 +20,13 @@ dj-database-url==0.3.0 psycopg2==2.5.4 # Files +django-mptt==0.6.1 django-filer==0.9.8 # Blog django-ckeditor-updated==4.4.4 + +# API +djoser==0.2.1 +djangorestframework==3.1.1 +django-cors-headers==1.0.0 diff --git a/fabfile.py b/fabfile.py index ce74215..8edfc2b 100644 --- a/fabfile.py +++ b/fabfile.py @@ -54,7 +54,7 @@ def setup_virtualenv(): Creates or updates a virtualenv """ print(yellow('Create virtualenv')) - local('virtualenv-2.7 .virtualenv') + local('virtualenv .virtualenv') with virtualenv(): print(yellow('Installing requirements')) diff --git a/rpm/scripts/shelfzilla b/rpm/scripts/shelfzilla index b1851ca..d99754e 100644 --- a/rpm/scripts/shelfzilla +++ b/rpm/scripts/shelfzilla @@ -29,7 +29,7 @@ function validations() { PID_PATH=/var/run/shelfzilla PID_FILE=${PID_PATH}/${INSTANCE}.pid P_USER="shelfzilla" - LOG_PATH=/var/log/shefzilla + LOG_PATH=/var/log/shelfzilla LOG_FILE=shelfzilla.log FCGI_PORT=8000 FCGI_IP=127.0.0.1 diff --git a/rpm/spec/shelfzilla.spec b/rpm/spec/shelfzilla.spec index c6aac2d..2969266 100644 --- a/rpm/spec/shelfzilla.spec +++ b/rpm/spec/shelfzilla.spec @@ -61,20 +61,20 @@ cp -r %{_gitdir}/rpm/scripts/shelfzilla $RPM_BUILD_ROOT%{_app_dir}/init/ #rmdir %{_app_dir}/init/ ## Npm install -#cd %{_app_dir} && npm install --production +cd %{_app_dir} && npm install --production ## pip install -#pip install -r %{_app_dir}/config/production/requirements.txt +pip install -r %{_app_dir}/config/production/requirements.txt ## Migrate -#python2.7 %{_app_dir}/manage.py migrate --no-initial-data +python2.7 %{_app_dir}/manage.py migrate --no-initial-data ## Bower -#cd %{_app_dir} -#bower install --allow-root +cd %{_app_dir} +bower install --allow-root ## Collect static -#python2.7 manage.py collectstatic --clear --noinput +python2.7 manage.py collectstatic --clear --noinput # -------------------------------------------------------------------------------------------- # # pre-uninstall section: @@ -101,5 +101,3 @@ rm -rf $RPM_BUILD_ROOT %{_app_dir}/* %{_app_dir}/.bowerrc %{_init_path}/shelfzilla - - diff --git a/shelfzilla/apps/account/admin.py b/shelfzilla/apps/account/admin.py index 6c72074..5d13745 100644 --- a/shelfzilla/apps/account/admin.py +++ b/shelfzilla/apps/account/admin.py @@ -103,3 +103,17 @@ class UserAdmin(DjangoUserAdmin): # Now register the new UserAdmin... admin.site.register(models.User, UserAdmin) admin.site.register(Permission) + + +class AccessCodeAdmin(admin.ModelAdmin): + list_display = ('code', 'max_uses', 'expiration', 'active', 'uses', + 'usable') + + def uses(self, obj): + return obj.uses + + def usable(self, obj): + return obj.usable + usable.boolean = True + +admin.site.register(models.AccessCode, AccessCodeAdmin) diff --git a/shelfzilla/apps/account/api/__init__.py b/shelfzilla/apps/account/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shelfzilla/apps/account/api/serializers.py b/shelfzilla/apps/account/api/serializers.py new file mode 100644 index 0000000..87cb4c2 --- /dev/null +++ b/shelfzilla/apps/account/api/serializers.py @@ -0,0 +1,12 @@ +# coding: utf-8 + +# django + +# third party +from rest_framework import serializers + +# own + + +class FeedSerializer(serializers.Serializer): + pass diff --git a/shelfzilla/apps/account/api/urls.py b/shelfzilla/apps/account/api/urls.py new file mode 100644 index 0000000..6f744d7 --- /dev/null +++ b/shelfzilla/apps/account/api/urls.py @@ -0,0 +1,12 @@ +# coding: utf-8 + +# third +from rest_framework.routers import DefaultRouter + +# own +from .views import FeedViewSet + + +router = DefaultRouter(trailing_slash=False) +router.register(r'feed', FeedViewSet, base_name='feed') +urlpatterns = router.urls diff --git a/shelfzilla/apps/account/api/views.py b/shelfzilla/apps/account/api/views.py new file mode 100644 index 0000000..cfdc114 --- /dev/null +++ b/shelfzilla/apps/account/api/views.py @@ -0,0 +1,43 @@ +# coding: utf-8 + +# python +from itertools import chain +import json + +# third +from rest_framework import viewsets +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated + +# own +from shelfzilla.apps.manga.models import ( + UserReadVolume, UserHaveVolume, UserWishlistVolume +) + + +class FeedViewSet(viewsets.ViewSet): + """ + """ + permission_classes = (IsAuthenticated,) + + def list(self, request): + owned_list = UserHaveVolume.objects.filter(user=request.user) + wishlisted_list = UserWishlistVolume.objects.filter(user=request.user) + read_list = UserReadVolume.objects.filter(user=request.user) + + timeline = sorted( + chain(owned_list, wishlisted_list, read_list), + key=lambda model: model.date, + reverse=True + )[:20] + + result = [] + for item in timeline: + event = { + 'date': item.date, + 'message': item.timeline_message, + 'type': item.event_type, + } + result.append(event) + + return Response(result) diff --git a/shelfzilla/apps/account/app.py b/shelfzilla/apps/account/app.py new file mode 100644 index 0000000..a1828e3 --- /dev/null +++ b/shelfzilla/apps/account/app.py @@ -0,0 +1,15 @@ +# coding: utf-8 + +# py3 +from __future__ import absolute_import + +# django +from django.apps import AppConfig + + +class AccountConfig(AppConfig): + name = 'account' + verbose_name = "Account" + + def ready(self): + from . import signals diff --git a/shelfzilla/apps/account/forms.py b/shelfzilla/apps/account/forms.py index b85e62d..880ddb9 100644 --- a/shelfzilla/apps/account/forms.py +++ b/shelfzilla/apps/account/forms.py @@ -1,10 +1,13 @@ from django import forms from django.contrib.auth import authenticate +from django.db import transaction from django.contrib.auth.forms import ( PasswordChangeForm as DjangoPasswordChangeForm ) from django.utils.translation import ugettext_lazy as _ +from . import models + class LoginForm(forms.Form): username = forms.CharField(max_length=75, label=_('Username')) @@ -43,3 +46,51 @@ class LoginForm(forms.Form): class PasswordChangeForm(DjangoPasswordChangeForm): pass + + +class RegistrationForm(forms.ModelForm): + """ + Custom for for registering an user + """ + password1 = forms.CharField(label=_('Password'), + widget=forms.PasswordInput) + password2 = forms.CharField(label=_('Repeat password'), + widget=forms.PasswordInput) + access_code = forms.CharField(label=_('Invitation code'), required=True) + + class Meta: + model = models.User + fields = ('email', 'username', ) + + def get_access_code(self): + try: + return models.AccessCode.objects.get( + code=self.cleaned_data['access_code']) + except models.AccessCode.DoesNotExist: + return False + + def clean_access_code(self): + code = self.get_access_code() + if not code or (code and not code.usable): + raise forms.ValidationError(_('Invitation code is not valid')) + + return self.cleaned_data['access_code'] + + def clean_password2(self): + # Check that the two password entries match + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise forms.ValidationError("Passwords don't match") + return password2 + + def save(self, commit=True): + # Save the provided password in hashed format + with transaction.atomic(): + user = super(RegistrationForm, self).save(commit=False) + access_code = self.get_access_code() + user.access_code = access_code + user.set_password(self.cleaned_data["password1"]) + if commit: + user.save() + return user diff --git a/shelfzilla/apps/account/migrations/0003_auto_20150110_1056.py b/shelfzilla/apps/account/migrations/0003_auto_20150110_1056.py new file mode 100644 index 0000000..9653ec8 --- /dev/null +++ b/shelfzilla/apps/account/migrations/0003_auto_20150110_1056.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0002_auto_20141111_1208'), + ] + + operations = [ + migrations.CreateModel( + name='AccessCode', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('code', models.CharField(max_length=128, verbose_name='Code')), + ('max_uses', models.IntegerField(default=1, verbose_name='Number of uses')), + ('expiration', models.DateTimeField(default=None, null=True, verbose_name='Expires')), + ('active', models.BooleanField(default=True, verbose_name='Active')), + ('user', models.ForeignKey(related_name='access_codes', blank=True, to=settings.AUTH_USER_MODEL, null=True)), + ], + options={ + 'verbose_name': 'Access code', + 'verbose_name_plural': 'Access codes', + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='user', + name='access_code', + field=models.ForeignKey(related_name='used_by', blank=True, to='account.AccessCode', null=True), + preserve_default=True, + ), + ] diff --git a/shelfzilla/apps/account/migrations/0004_auto_20150110_1058.py b/shelfzilla/apps/account/migrations/0004_auto_20150110_1058.py new file mode 100644 index 0000000..50db860 --- /dev/null +++ b/shelfzilla/apps/account/migrations/0004_auto_20150110_1058.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0003_auto_20150110_1056'), + ] + + operations = [ + migrations.AlterField( + model_name='accesscode', + name='expiration', + field=models.DateTimeField(default=None, null=True, verbose_name='Expires', blank=True), + preserve_default=True, + ), + ] diff --git a/shelfzilla/apps/account/models.py b/shelfzilla/apps/account/models.py index bc00560..88094b1 100644 --- a/shelfzilla/apps/account/models.py +++ b/shelfzilla/apps/account/models.py @@ -92,6 +92,11 @@ class User(AbstractBaseUser, USERNAME_FIELD = 'username' REQUIRED_FIELDS = ('email',) + # Access codes / Invitations + access_code = models.ForeignKey('account.AccessCode', + null=True, blank=True, + related_name='used_by') + objects = UserManager() class Meta: @@ -132,3 +137,40 @@ class User(AbstractBaseUser, return today.year - birthdate.year - ( (today.month, today.day) < (birthdate.month, birthdate_day) ) + + +class AccessCode(models.Model): + code = models.CharField(_('Code'), max_length=128) + max_uses = models.IntegerField(_('Number of uses'), default=1) + user = models.ForeignKey(User, null=True, blank=True, + related_name='access_codes') + expiration = models.DateTimeField(_('Expires'), null=True, blank=True, + default=None) + active = models.BooleanField(_('Active'), default=True) + + class Meta: + verbose_name = _('Access code') + verbose_name_plural = _('Access codes') + + def __unicode__(self): + return self.code + + @property + def uses(self): + return self.used_by.count() + + @property + def usable(self): + # Check if active + if not self.active: + return False + + # Check if expired + if self.expiration and (timezone.now() >= self.expiration): + return False + + # Check if it someone already used it + if self.used_by.count() == self.max_uses: + return False + + return True diff --git a/shelfzilla/apps/account/signals.py b/shelfzilla/apps/account/signals.py new file mode 100644 index 0000000..845cef6 --- /dev/null +++ b/shelfzilla/apps/account/signals.py @@ -0,0 +1,15 @@ +# coding: utf-8 + +# django +from django.dispatch import Signal +from django.dispatch import receiver + + +user_registered = Signal(providing_args=["user"]) + + +@receiver(user_registered) +def send_email_new_user(sender, **kwargs): + from shelfzilla.apps.mailing.emails import RegistrationEmail + mail = RegistrationEmail({"user": kwargs.get('user')}) + mail.send() diff --git a/shelfzilla/apps/account/urls.py b/shelfzilla/apps/account/urls.py index c4f2a3f..69711fa 100644 --- a/shelfzilla/apps/account/urls.py +++ b/shelfzilla/apps/account/urls.py @@ -1,10 +1,13 @@ from django.conf.urls import patterns, url -from .views import LoginView, LogoutView, UserProfileView, AccountView +from .views import ( + LoginView, LogoutView, UserProfileView, AccountView, RegisterView +) urlpatterns = patterns( '', url(r'^login/$', LoginView.as_view(), name="login"), + url(r'^register/$', RegisterView.as_view(), name="register"), url(r'^logout/$', LogoutView.as_view(), name="logout"), url( r'^user/(?P[\w\d\-\.]+)/$', diff --git a/shelfzilla/apps/account/views.py b/shelfzilla/apps/account/views.py index 3227dc4..2b03f89 100644 --- a/shelfzilla/apps/account/views.py +++ b/shelfzilla/apps/account/views.py @@ -2,7 +2,7 @@ from itertools import chain from django.views.generic import View from django.template import RequestContext from django.shortcuts import render_to_response, get_object_or_404 -from django.contrib.auth import logout +from django.contrib.auth import logout, authenticate, login from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.utils.translation import ugettext as _ @@ -11,8 +11,9 @@ from django.contrib import messages from django.contrib.auth import login from django.core.urlresolvers import reverse -from .forms import LoginForm, PasswordChangeForm +from .forms import LoginForm, PasswordChangeForm, RegistrationForm from .models import User +from .signals import user_registered from shelfzilla.apps.manga.models import ( UserReadVolume, UserHaveVolume, UserWishlistVolume ) @@ -140,3 +141,35 @@ class AccountView(View): ctx = RequestContext(request, data) return render_to_response(self.template, context_instance=ctx) + + +class RegisterView(View): + template = 'account/register.html' + form_class = RegistrationForm + + def get(self, request): + form_data = {} + + if 'code' in request.GET: + form_data['access_code'] = request.GET['code'] + + data = { + 'form': self.form_class(initial=form_data) + } + + ctx = RequestContext(request, data) + return render_to_response(self.template, context_instance=ctx) + + def post(self, request): + form = self.form_class(request.POST) + if form.is_valid(): + form.save() + user = authenticate(username=request.POST.get('username'), + password=request.POST.get('password1')) + login(request, user) + messages.success(request, _('Welcome to the community! :)')) + user_registered.send(sender=self.__class__, user=user) + return HttpResponseRedirect(reverse('homepage')) + + ctx = RequestContext(request, {'form': form}) + return render_to_response(self.template, context_instance=ctx) diff --git a/shelfzilla/apps/faq/__init__.py b/shelfzilla/apps/faq/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shelfzilla/apps/faq/admin.py b/shelfzilla/apps/faq/admin.py new file mode 100644 index 0000000..07b08be --- /dev/null +++ b/shelfzilla/apps/faq/admin.py @@ -0,0 +1,17 @@ +# coding: utf-8 + +# django +from django.contrib import admin + +# app +from .models import QuestionAnswerCategory, QuestionAnswer + + +class QuestionAnswerInline(admin.TabularInline): + model = QuestionAnswer + +class QuestionAnswerCategoryAdmin(admin.ModelAdmin): + inlines = (QuestionAnswerInline, ) + + +admin.site.register(QuestionAnswerCategory, QuestionAnswerCategoryAdmin) diff --git a/shelfzilla/apps/faq/migrations/0001_initial.py b/shelfzilla/apps/faq/migrations/0001_initial.py new file mode 100644 index 0000000..707a2c0 --- /dev/null +++ b/shelfzilla/apps/faq/migrations/0001_initial.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='QuestionAnswer', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('ord', models.PositiveIntegerField(default=1)), + ('title_es', models.CharField(max_length=256)), + ('answer_es', models.TextField()), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='QuestionAnswerCategory', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name_es', models.CharField(max_length=32)), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='questionanswer', + name='category', + field=models.ForeignKey(to='faq.QuestionAnswerCategory'), + preserve_default=True, + ), + ] diff --git a/shelfzilla/apps/faq/migrations/__init__.py b/shelfzilla/apps/faq/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shelfzilla/apps/faq/models.py b/shelfzilla/apps/faq/models.py new file mode 100644 index 0000000..6179958 --- /dev/null +++ b/shelfzilla/apps/faq/models.py @@ -0,0 +1,47 @@ +# coding: utf-8 + +# django +from django.db import models +from django.utils.translation import get_language, ugettext_lazy as _ + + +class QuestionAnswerCategory(models.Model): + name_es = models.CharField(max_length=32) + + class Meta: + ordering = ('name_es', ) + verbose_name = _('Category') + verbose_name_plural = _('Categories') + + def __unicode__(self): + return self.name + + @property + def name(self): + return getattr(self, u'name_{}'.format(get_language()), u'') + + +class QuestionAnswer(models.Model): + category = models.ForeignKey(QuestionAnswerCategory, + related_name='questions') + ord = models.PositiveIntegerField(default=1) + + # Spanish + title_es = models.CharField(max_length=256) + answer_es = models.TextField() + + class Meta: + ordering = ('ord', ) + verbose_name = _('Question') + verbose_name_plural = _('Questions') + + def __unicode__(self): + return self.title + + @property + def title(self): + return getattr(self, u'title_{}'.format(get_language()), u'') + + @property + def answer(self): + return getattr(self, u'answer_{}'.format(get_language()), u'') diff --git a/shelfzilla/apps/faq/tests.py b/shelfzilla/apps/faq/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/shelfzilla/apps/faq/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/shelfzilla/apps/faq/urls.py b/shelfzilla/apps/faq/urls.py new file mode 100644 index 0000000..8fa8d0a --- /dev/null +++ b/shelfzilla/apps/faq/urls.py @@ -0,0 +1,12 @@ +# coding: utf-8 + +# django +from django.conf.urls import patterns, url + +# app +from .views import FaqListView + +urlpatterns = patterns( + '', + url(r'^$', FaqListView.as_view(), name='faq.list'), +) diff --git a/shelfzilla/apps/faq/views.py b/shelfzilla/apps/faq/views.py new file mode 100644 index 0000000..0f30352 --- /dev/null +++ b/shelfzilla/apps/faq/views.py @@ -0,0 +1,26 @@ +# coding: utf-8 + +# django +from django.views.generic import View +from django.template import RequestContext +from django.shortcuts import render_to_response +from django.db.models import Count +from django.contrib.auth import get_user_model + +# shelfzilla.faq +from .models import QuestionAnswerCategory + + +class FaqListView(View): + template = 'faq/list.html' + + def get(self, request): + data = { + 'categories': QuestionAnswerCategory.objects.all(), + 'navigation': { + 'section': 'faqs', + }, + } + + ctx = RequestContext(request, data) + return render_to_response(self.template, context_instance=ctx) diff --git a/shelfzilla/apps/mailing/__init__.py b/shelfzilla/apps/mailing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shelfzilla/apps/mailing/emails.py b/shelfzilla/apps/mailing/emails.py new file mode 100644 index 0000000..17e1ede --- /dev/null +++ b/shelfzilla/apps/mailing/emails.py @@ -0,0 +1,16 @@ +# coding: utf-8 + +from django.utils.translation import ugettext_lazy as _ + +from .models import Email + + +class RegistrationEmail(Email): + template = 'mailing/registration.html' + subject = _('Bienvenido a Shelfzilla') + + # Context requires: + # - user: + + def prepare(self): + self.recipients.append(self.context['user'].email) diff --git a/shelfzilla/apps/mailing/models.py b/shelfzilla/apps/mailing/models.py new file mode 100644 index 0000000..9b8a323 --- /dev/null +++ b/shelfzilla/apps/mailing/models.py @@ -0,0 +1,53 @@ +# coding: utf-8 + +from django.template import Context, Template +from django.template.loader import get_template +from django.utils.html import strip_tags +from django.core.mail import EmailMultiAlternatives +from django.conf import settings + + +class Email(object): + template = '' + context = {} + + subject = '' + from_email = None + recipients = [] + text = '' + + def __init__(self, context={}): + self.from_email = getattr(settings, 'FROM_EMAIL', 'root@localhost') + self.context = context + self.recipients = [] + self.prepare() + + def prepare(self): + pass + + def compile_template(self): + tmpl = get_template(self.template) + self.html = tmpl.render(Context(self.context)) + self.text = strip_tags(self.html) + + def send(self): + if self.template: + self.compile_template() + + message = EmailMultiAlternatives(self.subject, + self.text, + self.from_email, + self.recipients) + + if self.template: + message.attach_alternative(self.html, "text/html") + + message.send() + + +# subject, from_email, to = 'hello', 'from@example.com', 'to@example.com' +# text_content = 'This is an important message.' +# html_content = '

This is an important message.

' +# msg = EmailMultiAlternatives(subject, text_content, from_email, [to]) +# msg.attach_alternative(html_content, "text/html") +# msg.send() diff --git a/shelfzilla/apps/manga/admin.py b/shelfzilla/apps/manga/admin.py index 2f98a70..f9c987c 100644 --- a/shelfzilla/apps/manga/admin.py +++ b/shelfzilla/apps/manga/admin.py @@ -88,11 +88,13 @@ admin.site.register(Publisher, PublisherAdmin) class SeriesSummaryInline(admin.TabularInline): model = SeriesSummary fields = ('summary', 'language', ) + suit_classes = 'suit-tab suit-tab-summaries' class SeriesPublisherInline(admin.TabularInline): model = SeriesPublisher fields = ('publisher', 'status', 'actual_publisher') + suit_classes = 'suit-tab suit-tab-publishers' class SeriesAdmin(ImportExportModelAdmin, reversion.VersionAdmin): @@ -107,7 +109,9 @@ class SeriesAdmin(ImportExportModelAdmin, reversion.VersionAdmin): suit_form_tabs = ( ('general', _('General')), + ('publishers', _('Publishers')), ('volumes', _('Volumes')), + ('summaries', _('Summary')), ('review', _('Review')), ('advanced', _('Advanced')), ) @@ -198,6 +202,7 @@ admin.site.register(Volume, VolumeAdmin) class PersonAdmin(ImportExportModelAdmin, reversion.VersionAdmin): resource_class = PersonResource + search_fields = ('name', ) suit_form_tabs = ( ('general', _('General')), ('review', _('Review')), diff --git a/shelfzilla/apps/manga/api/__init__.py b/shelfzilla/apps/manga/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/shelfzilla/apps/manga/api/serializers.py b/shelfzilla/apps/manga/api/serializers.py new file mode 100644 index 0000000..617195e --- /dev/null +++ b/shelfzilla/apps/manga/api/serializers.py @@ -0,0 +1,59 @@ +# coding: utf-8 + +# django +from django.conf import settings + +# third party +from easy_thumbnails.files import get_thumbnailer +from rest_framework import serializers + +# own +from ..models import Volume, Series, Publisher, Person, Language + + +class LanguageSerializer(serializers.ModelSerializer): + class Meta: + model = Language + fields = ('name', ) + + +class PersonSerializer(serializers.ModelSerializer): + class Meta: + model = Person + fields = ('name',) + + +class PublisherSerializer(serializers.ModelSerializer): + class Meta: + model = Publisher + fields = ('id', 'name', 'url', ) + + +class SeriesSerializer(serializers.ModelSerializer): + original_publisher = PublisherSerializer() + art = PersonSerializer(many=True) + story = PersonSerializer(many=True) + + class Meta: + model = Series + fields = ('id', 'name', 'status', 'art', 'story', 'original_publisher') + + +class VolumeSerializer(serializers.ModelSerializer): + series = SeriesSerializer() + publisher = PublisherSerializer() + language = LanguageSerializer() + cover = serializers.SerializerMethodField('get_cover_thumbnail') + + def get_cover_thumbnail(self, obj): + if obj.cover: + url = get_thumbnailer(obj.cover).get_thumbnail({ + 'size': (100, 100), 'crop': 'scale', 'autocrop': True, + }).url + return url + return None + + class Meta: + model = Volume + fields = ('id', 'series', 'number', 'name', 'retail_price', + 'release_date', 'publisher', 'cover', 'language') diff --git a/shelfzilla/apps/manga/api/urls.py b/shelfzilla/apps/manga/api/urls.py new file mode 100644 index 0000000..ec1e85f --- /dev/null +++ b/shelfzilla/apps/manga/api/urls.py @@ -0,0 +1,13 @@ +# coding: utf-8 + +# third +from rest_framework.routers import DefaultRouter + +# own +from .views import VolumesViewSet + + +router = DefaultRouter(trailing_slash=False) +router.register(r'volumes', VolumesViewSet) + +urlpatterns = router.urls diff --git a/shelfzilla/apps/manga/api/views.py b/shelfzilla/apps/manga/api/views.py new file mode 100644 index 0000000..ca1fdbd --- /dev/null +++ b/shelfzilla/apps/manga/api/views.py @@ -0,0 +1,22 @@ +# coding: utf-8 + +# third +from rest_framework import viewsets, filters +from rest_framework.permissions import IsAuthenticated + +# own +from .serializers import VolumeSerializer +from ..models import Volume + + +class VolumesViewSet(viewsets.ReadOnlyModelViewSet): + """ + """ + # permission_classes = (IsAuthenticated,) + serializer_class = VolumeSerializer + queryset = Volume.objects.filter(hidden=False) + paginate_by = 20 + + filter_fields = ('series', ) + filter_backends = (filters.SearchFilter,) + search_fields = ('name', 'number', 'series__name', ) diff --git a/shelfzilla/apps/manga/models.py b/shelfzilla/apps/manga/models.py index f7a0c2a..0435a0d 100644 --- a/shelfzilla/apps/manga/models.py +++ b/shelfzilla/apps/manga/models.py @@ -261,7 +261,7 @@ class UserHaveVolume(models.Model): return self._timeline_message % {'volume': self.volume} def __unicode__(self): - return "{} {} {}".format( + return u"{} {} {}".format( self.user.username, _('have'), self.volume @@ -289,7 +289,7 @@ class UserWishlistVolume(models.Model): return self._timeline_message % {'volume': self.volume} def __unicode__(self): - return "{} {} {}".format( + return u"{} {} {}".format( self.user.username, _('wants'), self.volume @@ -317,7 +317,7 @@ class UserReadVolume(models.Model): return self._timeline_message % {'volume': self.volume} def __unicode__(self): - return "{} {} {}".format( + return u"{} {} {}".format( self.user.username, _('have read'), self.volume @@ -386,8 +386,11 @@ def volume_check_filer(sender, instance, created, **kwargs): def series_delete_folder(sender, instance, using, **kwargs): - if instance.folder: - instance.folder.delete() + try: + if instance.folder: + instance.folder.delete() + except: + pass def volume_delete_cover(sender, instance, **kwargs): diff --git a/shelfzilla/locale/es/LC_MESSAGES/django.mo b/shelfzilla/locale/es/LC_MESSAGES/django.mo index e2860e3..eecff1f 100644 Binary files a/shelfzilla/locale/es/LC_MESSAGES/django.mo and b/shelfzilla/locale/es/LC_MESSAGES/django.mo differ diff --git a/shelfzilla/locale/es/LC_MESSAGES/django.po b/shelfzilla/locale/es/LC_MESSAGES/django.po index fad81ad..affc41d 100644 --- a/shelfzilla/locale/es/LC_MESSAGES/django.po +++ b/shelfzilla/locale/es/LC_MESSAGES/django.po @@ -8,9 +8,9 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-09-09 01:10+0200\n" -"PO-Revision-Date: 2014-09-09 01:11+0200\n" -"Last-Translator: Felipe Martin \n" +"POT-Creation-Date: 2015-01-26 14:45+0100\n" +"PO-Revision-Date: 2015-01-26 14:45+0100\n" +"Last-Translator: \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,18 +19,6 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Translated-Using: django-rosetta 0.7.4\n" -#: models.py:7 -msgid "For review" -msgstr "Para revisión" - -#: models.py:9 -msgid "Review comment" -msgstr "Comentario de revisión" - -#: models.py:11 -msgid "Hidden" -msgstr "Oculto" - #: apps/_admin/views.py:44 apps/_admin/views.py:81 msgid "Volume series changed" msgstr "Serie de los volúmenes cambiada" @@ -43,6 +31,160 @@ msgstr "No se encontró la URL de la carátula para actualizar." msgid "Volume not found." msgstr "Volumen no encontrado." +#: apps/account/admin.py:85 +#, fuzzy +#| msgid "Person" +msgid "Personal info" +msgstr "Persona" + +#: apps/account/admin.py:87 +msgid "Permissions" +msgstr "Permisos" + +#: apps/account/admin.py:88 +msgid "Information" +msgstr "Información" + +#: apps/account/forms.py:13 apps/account/models.py:40 +msgid "Username" +msgstr "Usuario" + +#: apps/account/forms.py:15 apps/account/forms.py:55 +msgid "Password" +msgstr "Contraseña" + +#: apps/account/forms.py:37 +msgid "This account is disabled." +msgstr "Esta cuenta está desactivada." + +#: apps/account/forms.py:41 +msgid "User with those credentials was not found." +msgstr "No se ha encontrado un usuario con esos credenciales." + +#: apps/account/forms.py:57 +msgid "Repeat password" +msgstr "Repetir contraseña" + +#: apps/account/forms.py:59 +msgid "Invitation code" +msgstr "Código de invitacion" + +#: apps/account/forms.py:75 +msgid "Invitation code is not valid" +msgstr "El código de invitación no es válido" + +#: apps/account/models.py:19 +msgid "Male" +msgstr "Hombre" + +#: apps/account/models.py:20 +msgid "Female" +msgstr "Mujer" + +#: apps/account/models.py:32 +msgid "Email address" +msgstr "Correo electrónico" + +#: apps/account/models.py:36 +msgid "An user email that should be verified." +msgstr "Una dirección de correo electrónico que será verificada" + +#: apps/account/models.py:48 +msgid "First name" +msgstr "Nombre" + +#: apps/account/models.py:53 +msgid "Last name" +msgstr "Apellidos" + +#: apps/account/models.py:58 +msgid "Birthdate" +msgstr "Fecha de nacimiento" + +#: apps/account/models.py:65 +msgid "Gender" +msgstr "Sexo" + +#: apps/account/models.py:73 +msgid "Date joined" +msgstr "Fecha de registro" + +#: apps/account/models.py:79 +msgid "Active status" +msgstr "" + +#: apps/account/models.py:81 +msgid "" +"Designates whether this user should be treated as active. Unselect this " +"instead of deleting accounts." +msgstr "" + +#: apps/account/models.py:86 +msgid "Staff status" +msgstr "" + +#: apps/account/models.py:88 +msgid "Designates whether the user can log into this admin site." +msgstr "" + +#: apps/account/models.py:103 +msgid "User" +msgstr "Usuario" + +#: apps/account/models.py:143 apps/manga/models.py:232 +msgid "Code" +msgstr "Código" + +#: apps/account/models.py:144 +msgid "Number of uses" +msgstr "Número de usos" + +#: apps/account/models.py:147 +msgid "Expires" +msgstr "Expira" + +#: apps/account/models.py:149 +msgid "Active" +msgstr "Activo" + +#: apps/account/models.py:152 +msgid "Access code" +msgstr "Código de acceso" + +#: apps/account/models.py:153 +msgid "Access codes" +msgstr "Códigos de acceso" + +#: apps/account/views.py:48 +msgid "Logged in successfully." +msgstr "Has accedido correctamente." + +#: apps/account/views.py:67 +msgid "Logged out successfully" +msgstr "Sesión finalizada." + +#: apps/account/views.py:134 +msgid "Password changed." +msgstr "Contraseña cambiada." + +#: apps/account/views.py:165 +msgid "Welcome to the community! :)" +msgstr "¡Bienvenido a la comunidad! :)" + +#: apps/blog/admin.py:24 apps/manga/admin.py:61 apps/manga/admin.py:109 +#: apps/manga/admin.py:154 apps/manga/admin.py:202 apps/manga/admin.py:229 +#: apps/manga/admin.py:254 +msgid "General" +msgstr "General" + +#: apps/blog/admin.py:25 +msgid "Content" +msgstr "Contenido" + +#: apps/blog/templatetags/datetime.py:17 +msgid "%B %e, %Y" +msgstr "" + #: apps/config/models.py:13 apps/config/models.py:16 apps/config/models.py:17 msgid "Site Configuration" msgstr "Configuración del sitio" @@ -51,6 +193,10 @@ msgstr "Configuración del sitio" msgid "Social Configuration" msgstr "Configuración social" +#: apps/mailing/emails.py:10 +msgid "Bienvenido a Shelfzilla" +msgstr "" + #: apps/manga/admin.py:44 msgid "Items marked for review" msgstr "Marcar items para revisión" @@ -59,11 +205,6 @@ msgstr "Marcar items para revisión" msgid "Items unmarked for review" msgstr "Desmarcar items para revisión" -#: apps/manga/admin.py:61 apps/manga/admin.py:109 apps/manga/admin.py:154 -#: apps/manga/admin.py:202 apps/manga/admin.py:229 apps/manga/admin.py:254 -msgid "General" -msgstr "General" - #: apps/manga/admin.py:62 apps/manga/admin.py:111 apps/manga/admin.py:156 #: apps/manga/admin.py:203 apps/manga/admin.py:255 msgid "Review" @@ -75,7 +216,7 @@ msgid "Advanced" msgstr "Avanzado" #: apps/manga/admin.py:83 apps/manga/models.py:134 apps/manga/models.py:135 -#: themes/bootflat/templates/_layout.html:40 +#: themes/bootflat/templates/_layout.html:39 #: themes/bootflat/templates/homepage/home.html:72 #: themes/bootflat/templates/manga/publishers/detail.html:19 #: themes/bootflat/templates/manga/series/list.html:4 @@ -120,7 +261,7 @@ msgstr "URL" msgid "Publisher" msgstr "Editorial" -#: apps/manga/models.py:54 themes/bootflat/templates/_layout.html:43 +#: apps/manga/models.py:54 themes/bootflat/templates/_layout.html:42 #: themes/bootflat/templates/manga/publishers/list.html:4 #: themes/bootflat/templates/manga/series/detail.html:55 msgid "Publishers" @@ -206,10 +347,6 @@ msgstr "Persona" msgid "Persons" msgstr "Personas" -#: apps/manga/models.py:232 -msgid "Code" -msgstr "Código" - #: apps/manga/models.py:239 #: themes/bootflat/templates/manga/series/detail.html:106 msgid "Language" @@ -219,7 +356,7 @@ msgstr "Idioma" msgid "Languages" msgstr "Idiomas" -#: apps/manga/models.py:250 apps/manga/models.py:273 apps/manga/models.py:296 +#: apps/manga/models.py:250 apps/manga/models.py:278 apps/manga/models.py:306 msgid "Date" msgstr "Fecha" @@ -228,32 +365,32 @@ msgstr "Fecha" msgid "%(volume)s added to collection" msgstr "%(volume)s añadido a la colección" -#: apps/manga/models.py:261 +#: apps/manga/models.py:266 msgid "have" msgstr "tiene" -#: apps/manga/models.py:275 +#: apps/manga/models.py:280 #, python-format msgid "%(volume)s wishlisted" msgstr "%(volume)s añadido a deseados" -#: apps/manga/models.py:284 +#: apps/manga/models.py:294 msgid "wants" msgstr "quiere" -#: apps/manga/models.py:298 +#: apps/manga/models.py:308 #, python-format msgid "%(volume)s marked as read" msgstr "%(volume)s marcado como leído" -#: apps/manga/models.py:307 +#: apps/manga/models.py:322 msgid "have read" msgstr "ha leído" #: apps/manga/views/search.py:11 apps/manga/views/search.py:13 -#: themes/bootflat/templates/_layout.html:80 #: themes/bootflat/templates/_admin/volumes/includes/cover.html:6 #: themes/bootflat/templates/_admin/volumes/includes/cover.html:15 +#: themes/bootflat/templates/_layout.html:83 msgid "Search" msgstr "Buscar" @@ -285,31 +422,19 @@ msgstr "{} marcado como no leído" msgid "{} marked as read!" msgstr "¡{} marcado como leído!" -#: apps/users/forms.py:7 -msgid "Username" -msgstr "Usuario" +#: models.py:7 +msgid "For review" +msgstr "Para revisión" -#: apps/users/forms.py:9 -msgid "Password" -msgstr "Contraseña" +#: models.py:9 +msgid "Review comment" +msgstr "Comentario de revisión" -#: apps/users/forms.py:31 -msgid "This account is disabled." -msgstr "Esta cuenta está desactivada." +#: models.py:11 +msgid "Hidden" +msgstr "Oculto" -#: apps/users/forms.py:35 -msgid "User with those credentials was not found." -msgstr "No se ha encontrado un usuario con esos credenciales." - -#: apps/users/views.py:44 -msgid "Logged in successfully." -msgstr "Has accedido correctamente." - -#: apps/users/views.py:63 -msgid "Logged out successfully" -msgstr "Sesión finalizada." - -#: settings/base.py:128 +#: settings/base.py:131 msgid "Spanish" msgstr "Español" @@ -329,25 +454,6 @@ msgstr "" "Hubo un error interno del servidor. Puede ser temporal, pero si el problema " "persiste, contacta con nosotros." -#: themes/bootflat/templates/_layout.html:27 -msgid "Toggle navigation" -msgstr "Mostrar/Ocultar navegación" - -#: themes/bootflat/templates/_layout.html:49 -#: themes/bootflat/templates/_layout.html:57 -#: themes/bootflat/templates/users/profile-pjax.html:4 -#: themes/bootflat/templates/users/profile.html:4 -msgid "Profile" -msgstr "Perfil" - -#: themes/bootflat/templates/_layout.html:62 -msgid "Logout" -msgstr "Cerrar sesión" - -#: themes/bootflat/templates/_layout.html:69 -msgid "Log in" -msgstr "Entrar" - #: themes/bootflat/templates/_admin/manga/series/includes/volumes.html:13 msgid "Edit" msgstr "Editar" @@ -361,6 +467,7 @@ msgid "Change to: " msgstr "Cambiar a:" #: themes/bootflat/templates/_admin/volumes/change_series.html:28 +#: themes/bootflat/templates/account/main.html:61 msgid "Change" msgstr "Cambiar" @@ -372,6 +479,41 @@ msgstr "Carátula actual:" msgid "Update with this" msgstr "Actualizar con esta" +#: themes/bootflat/templates/_layout.html:27 +msgid "Toggle navigation" +msgstr "Mostrar/Ocultar navegación" + +#: themes/bootflat/templates/_layout.html:47 +#: themes/bootflat/templates/_layout.html:55 +#: themes/bootflat/templates/users/profile-pjax.html:4 +#: themes/bootflat/templates/users/profile.html:4 +msgid "Profile" +msgstr "Perfil" + +#: themes/bootflat/templates/_layout.html:60 +msgid "Logout" +msgstr "Cerrar sesión" + +#: themes/bootflat/templates/_layout.html:67 +#: themes/bootflat/templates/account/register.html:15 +#: themes/bootflat/templates/account/register.html:46 +msgid "Register" +msgstr "Registrarse" + +#: themes/bootflat/templates/_layout.html:72 +msgid "Log in" +msgstr "Entrar" + +#: themes/bootflat/templates/account/_layout.html:4 +msgid "Account" +msgstr "" + +#: themes/bootflat/templates/account/main.html:31 +#, fuzzy +#| msgid "Change to: " +msgid "Change password" +msgstr "Cambiar a:" + #: themes/bootflat/templates/homepage/home.html:10 msgid "Upcoming volumes" msgstr "Futuros lanzamientos" @@ -397,6 +539,16 @@ msgstr "Estadísticas" msgid "Users" msgstr "Usuarios" +#: themes/bootflat/templates/manga/publishers/detail.html:10 +#: themes/bootflat/templates/manga/series/detail.html:10 +#: themes/bootflat/templates/manga/series/includes/volume.html:52 +msgid "Edit in admin" +msgstr "Editar en el admin" + +#: themes/bootflat/templates/manga/publishers/detail.html:26 +msgid "Homepage" +msgstr "Página principal" + #: themes/bootflat/templates/manga/search.html:8 msgid "Looking for..." msgstr "Buscando..." @@ -407,16 +559,6 @@ msgstr "Buscando..." msgid "No results" msgstr "Sin resultados" -#: themes/bootflat/templates/manga/publishers/detail.html:10 -#: themes/bootflat/templates/manga/series/detail.html:10 -#: themes/bootflat/templates/manga/series/includes/volume.html:47 -msgid "Edit in admin" -msgstr "Editar en el admin" - -#: themes/bootflat/templates/manga/publishers/detail.html:26 -msgid "Homepage" -msgstr "Página principal" - #: themes/bootflat/templates/manga/series/detail.html:26 msgid "Art" msgstr "Arte" @@ -443,20 +585,20 @@ msgstr "Todos" msgid "Filter" msgstr "Filtrar" -#: themes/bootflat/templates/manga/series/list.html:17 -msgid "other" -msgstr "otros" - -#: themes/bootflat/templates/manga/series/includes/volume.html:39 +#: themes/bootflat/templates/manga/series/includes/volume.html:44 #, python-format msgid "%(pages)s pages" msgstr "%(pages)s páginas" -#: themes/bootflat/templates/users/login.html:24 +#: themes/bootflat/templates/manga/series/list.html:17 +msgid "other" +msgstr "otros" + +#: themes/bootflat/templates/users/login.html:27 msgid "Access the site" msgstr "Accede al sitio" -#: themes/bootflat/templates/users/login.html:41 +#: themes/bootflat/templates/users/login.html:45 msgid "Login" msgstr "Entrar" @@ -469,10 +611,6 @@ msgstr "Lista de deseados" msgid "Edit my profile" msgstr "Editar mi perfil" -#: themes/bootflat/templates/users/profile.html:50 -msgid "My preferences" -msgstr "Mis preferencias" - #: themes/bootflat/templates/users/profile/achievements.html:4 msgid "Achievements" msgstr "Logros" @@ -494,6 +632,9 @@ msgstr "Volúmenes" msgid "Volumes wishlisted" msgstr "Deseados" +#~ msgid "My preferences" +#~ msgstr "Mis preferencias" + #~ msgid "Requires review" #~ msgstr "Requiere revisión" diff --git a/shelfzilla/settings/base.py b/shelfzilla/settings/base.py index b82c226..b4a521c 100644 --- a/shelfzilla/settings/base.py +++ b/shelfzilla/settings/base.py @@ -65,10 +65,18 @@ INSTALLED_APPS = ( 'shelfzilla.apps._admin', 'shelfzilla.apps.config', 'shelfzilla.apps.homepage', - 'shelfzilla.apps.landing', + # 'shelfzilla.apps.landing', + 'shelfzilla.apps.mailing', 'shelfzilla.apps.manga', 'shelfzilla.apps.blog', + 'shelfzilla.apps.faq', 'shelfzilla.apps.pjax', + + # API + 'corsheaders', + 'rest_framework', + 'rest_framework.authtoken', + 'djoser', ) TEMPLATE_CONTEXT_PROCESSORS = ( @@ -93,6 +101,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( MIDDLEWARE_CLASSES = ( 'reversion.middleware.RevisionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -255,7 +264,7 @@ SUIT_CONFIG = { { 'label': 'Authorization', 'icon': 'icon-lock', - 'models': ('account.user', 'auth.group') + 'models': ('account.user', 'auth.group', 'account.accesscode', ) }, { 'app': 'config', @@ -272,6 +281,11 @@ SUIT_CONFIG = { 'label': 'Manga', 'icon': 'icon-book', }, + { + 'app': 'faq', + 'label': 'FAQs', + 'icon': 'icon-book', + }, { 'label': 'Files', 'icon': 'icon-file', @@ -296,3 +310,24 @@ CKEDITOR_CONFIGS = { # AUTH # AUTH_USER_MODEL = 'account.User' + + +# +# API +# +CORS_ORIGIN_ALLOW_ALL = True + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.TokenAuthentication', + ), +} + +DJOSER = { + 'DOMAIN': 'shelfzilla.com', + 'SITE_NAME': 'Shelfzilla', + 'PASSWORD_RESET_CONFIRM_URL': '#/password/reset/confirm/{uid}/{token}', + 'ACTIVATION_URL': '#/activate/{uid}/{token}', + 'LOGIN_AFTER_ACTIVATION': True, + 'SEND_ACTIVATION_EMAIL': False, +} diff --git a/shelfzilla/settings/configfile.py b/shelfzilla/settings/configfile.py index 0a7002b..17a93e6 100644 --- a/shelfzilla/settings/configfile.py +++ b/shelfzilla/settings/configfile.py @@ -14,6 +14,17 @@ with open(os.environ['APP_CONFIGFILE']) as conffile: # Installed Apps INSTALLED_APPS += tuple(config['global']['installed_apps']) +# Middleware classes +if 'middleware_classes' in config['global']: + if 'prepend' in config['global']['middleware_classes']: + MIDDLEWARE_CLASSES = MIDDLEWARE_CLASSES + tuple( + config['global']['middleware_classes']['prepend'] + ) + if 'append' in config['global']['middleware_classes']: + MIDDLEWARE_CLASSES += tuple( + config['global']['middleware_classes']['append'] + ) + # Database DATABASES = { 'default': dj_database_url.parse(config['global']['database_url']) @@ -75,13 +86,27 @@ FILER_STORAGES = { # Logging LOGGING = { 'version': 1, - 'disable_existing_loggers': False, + 'disable_existing_loggers': True, + 'formatters': { + 'verbose': { + 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' + }, + }, 'handlers': { 'file': { 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': config['log']['logfile'], }, + 'opbeat': { + 'level': 'WARNING', + 'class': 'opbeat.contrib.django.handlers.OpbeatHandler', + }, + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'verbose' + } }, 'loggers': { 'django.request': { @@ -89,5 +114,21 @@ LOGGING = { 'level': 'DEBUG', 'propagate': True, }, + 'django.db.backends': { + 'level': 'ERROR', + 'handlers': ['console'], + 'propagate': False, + }, + 'shelfzilla': { + 'level': 'WARNING', + 'handlers': ['opbeat'], + 'propagate': False, + }, + # Log errors from the Opbeat module to the console (recommended) + 'opbeat.errors': { + 'level': 'ERROR', + 'handlers': ['console'], + 'propagate': False, + }, }, } diff --git a/shelfzilla/settings/local.py b/shelfzilla/settings/local.py index 76fba99..13e86bd 100644 --- a/shelfzilla/settings/local.py +++ b/shelfzilla/settings/local.py @@ -30,3 +30,8 @@ FILER_DUMP_PAYLOAD = True MEDIA_URL = '/media/' STATIC_URL = '/static/' + +EMAIL_BACKEND = 'django_mailgun.MailgunBackend' +MAILGUN_ACCESS_KEY = 'key-fdfc57f2bfb35a4ba5f9c1e3c30af373' +MAILGUN_SERVER_NAME = 'sandbox2dd21e486d144dc59742738b15e494ee.mailgun.org' +FROM_EMAIL = 'info@shelfzilla.com' diff --git a/shelfzilla/themes/bootflat/static/images/flags/en-uk.gif b/shelfzilla/themes/bootflat/static/images/flags/en-uk.gif new file mode 100644 index 0000000..3c6bce1 Binary files /dev/null and b/shelfzilla/themes/bootflat/static/images/flags/en-uk.gif differ diff --git a/shelfzilla/themes/bootflat/static/images/flags/en-us.gif b/shelfzilla/themes/bootflat/static/images/flags/en-us.gif new file mode 100755 index 0000000..8f198f7 Binary files /dev/null and b/shelfzilla/themes/bootflat/static/images/flags/en-us.gif differ diff --git a/shelfzilla/themes/bootflat/static/images/flags/fr.gif b/shelfzilla/themes/bootflat/static/images/flags/fr.gif new file mode 100755 index 0000000..43d0b80 Binary files /dev/null and b/shelfzilla/themes/bootflat/static/images/flags/fr.gif differ diff --git a/shelfzilla/themes/bootflat/static/images/flags/it.gif b/shelfzilla/themes/bootflat/static/images/flags/it.gif new file mode 100755 index 0000000..d79e90e Binary files /dev/null and b/shelfzilla/themes/bootflat/static/images/flags/it.gif differ diff --git a/shelfzilla/themes/bootflat/static/images/flags/pt.gif b/shelfzilla/themes/bootflat/static/images/flags/pt.gif new file mode 100755 index 0000000..e735f74 Binary files /dev/null and b/shelfzilla/themes/bootflat/static/images/flags/pt.gif differ diff --git a/shelfzilla/themes/bootflat/templates/_admin/manga/series/includes/volumes.html b/shelfzilla/themes/bootflat/templates/_admin/manga/series/includes/volumes.html index 47ce4d8..cd374e7 100644 --- a/shelfzilla/themes/bootflat/templates/_admin/manga/series/includes/volumes.html +++ b/shelfzilla/themes/bootflat/templates/_admin/manga/series/includes/volumes.html @@ -1,4 +1,9 @@ {% load i18n %} + +{% if original %} +{% trans "Add" %} {% trans "Volume" %} +
+
@@ -15,4 +20,4 @@ {% endfor %}
 
- +{% endif %} diff --git a/shelfzilla/themes/bootflat/templates/_includes/google_analytics.html b/shelfzilla/themes/bootflat/templates/_includes/google_analytics.html index 33f38e1..e11d504 100644 --- a/shelfzilla/themes/bootflat/templates/_includes/google_analytics.html +++ b/shelfzilla/themes/bootflat/templates/_includes/google_analytics.html @@ -1,9 +1,12 @@ - diff --git a/shelfzilla/themes/bootflat/templates/_layout.html b/shelfzilla/themes/bootflat/templates/_layout.html index 29485a1..241e285 100644 --- a/shelfzilla/themes/bootflat/templates/_layout.html +++ b/shelfzilla/themes/bootflat/templates/_layout.html @@ -15,6 +15,9 @@ {% endblock %} {% endcompress %} {% block page_title %}ShelfZilla{% endblock %} + {% if social_config.google_analytics %} + {% include "_includes/google_analytics.html" %} + {% endif %} {% block navigation_bar %} @@ -34,7 +37,6 @@