diff --git a/.gitignore b/.gitignore index a204982..ec7b198 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea *.pyc build/ dist/ diff --git a/favit/admin.py b/favit/admin.py index f87ae59..88091a3 100644 --- a/favit/admin.py +++ b/favit/admin.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- + from django.contrib import admin from .models import Favorite diff --git a/favit/managers.py b/favit/managers.py index b954f87..977e9de 100644 --- a/favit/managers.py +++ b/favit/managers.py @@ -1,18 +1,16 @@ -# -*- coding: utf-8 -*- + from django.contrib.contenttypes.models import ContentType from django.db import models +from django.apps import apps +from builtins import str +from builtins import int -try: - from django.db.models import get_model -except ImportError: - from django.apps import apps - get_model = apps.get_model def _get_content_type_and_obj(obj, model=None): - if isinstance(model, basestring): - model = get_model(*model.split(".")) + if isinstance(model, str): + model = apps.get_model(*model.split(".")) - if isinstance(obj, (int, long)): + if isinstance(obj, int): obj = model.objects.get(pk=obj) return ContentType.objects.get_for_model(type(obj)), obj @@ -44,8 +42,8 @@ def for_user(self, user, model=None): qs = self.get_query_set().filter(user=user) if model: - if isinstance(model, basestring): - model = get_model(*model.split(".")) + if isinstance(model, str): + model = apps.get_model(*model.split(".")) content_type = ContentType.objects.get_for_model(model) qs = qs.filter(target_content_type=content_type) @@ -65,8 +63,8 @@ def for_model(self, model): """ # if model is an app_label.model string make it a Model class - if isinstance(model, basestring): - model = get_model(*model.split(".")) + if isinstance(model, str): + model = apps.get_model(*model.split(".")) content_type = ContentType.objects.get_for_model(model) diff --git a/favit/migrations/0001_initial.py b/favit/migrations/0001_initial.py new file mode 100644 index 0000000..d0aa4fd --- /dev/null +++ b/favit/migrations/0001_initial.py @@ -0,0 +1,36 @@ + + + +from django.db import migrations, models +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Favorite', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('target_object_id', models.PositiveIntegerField()), + ('timestamp', models.DateTimeField(auto_now_add=True, db_index=True)), + ('target_content_type', models.ForeignKey(on_delete=models.deletion.CASCADE, to='contenttypes.ContentType')), + ('user', models.ForeignKey(on_delete=models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-timestamp'], + 'get_latest_by': 'timestamp', + 'verbose_name': 'favorite', + 'verbose_name_plural': 'favorites', + }, + ), + migrations.AlterUniqueTogether( + name='favorite', + unique_together=set([('user', 'target_content_type', 'target_object_id')]), + ), + ] diff --git a/favit/migrations/__init__.py b/favit/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/favit/models.py b/favit/models.py index 2cb6c28..b3270b6 100644 --- a/favit/models.py +++ b/favit/models.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- + try: from django.contrib.contenttypes.generic import GenericForeignKey except ImportError: @@ -16,8 +16,8 @@ class Favorite(models.Model): """ """ - user = models.ForeignKey(getattr(settings, 'AUTH_USER_MODEL', 'auth.User')) - target_content_type = models.ForeignKey(ContentType) + user = models.ForeignKey(getattr(settings, 'AUTH_USER_MODEL', 'auth.User'), on_delete=models.CASCADE) + target_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) target_object_id = models.PositiveIntegerField() target = GenericForeignKey('target_content_type', 'target_object_id') timestamp = models.DateTimeField(auto_now_add=True, db_index=True) diff --git a/favit/static/favit/css/favorite.css b/favit/static/favit/css/favorite.css index 223a5e6..22cad30 100644 --- a/favit/static/favit/css/favorite.css +++ b/favit/static/favit/css/favorite.css @@ -1,3 +1,11 @@ +.favit i.fa-heart { + color: #CB3D3D; +} + +.favit.fa-heart-o { + color: black; +} + .fav-count { font-size: .8em; padding: 2px 5px 2px 4px; diff --git a/favit/static/favit/js/favorite.js b/favit/static/favit/js/favorite.js index c3bd14d..6dcaf7e 100644 --- a/favit/static/favit/js/favorite.js +++ b/favit/static/favit/js/favorite.js @@ -1,43 +1,59 @@ -$(document).ready(function() { - $('.btn.favorite').click(function() { - var $obj = $(this); - var target_id = $obj.attr('id').split('_')[1]; - $obj.prop('disabled', true); - $.ajax({ - url: $obj.attr('href'), - type: 'POST', - data: {target_model: $obj.attr('model'), - target_object_id: target_id}, - success: function(response) { - if (response.status == 'added') { - $obj.children().removeClass('icon-heart-empty').addClass('icon-heart');} - else { - $obj.children().removeClass('icon-heart').addClass('icon-heart-empty'); - } - $obj.parent('.favit').children('.fav-count').text(response.fav_count); - $obj.prop('disabled', false); - } - }); - }); +jQuery(document).ready(function ($) { + $('.btn.favorite').click(function () { + var $obj = $(this); + var target_id = $obj.attr('id').split('_')[1]; - $('.btn.unfave').click(function() { - var $obj = $(this); - $obj.prop('disabled', true); - $.ajax({ - url: $obj.attr('href'), - type: 'POST', - data: { - target_model: $obj.data('model'), - target_object_id: $obj.data('id') - }, - success: function(response) { - if (response.status == 'deleted') { - $obj.parent().remove(); - } - }, - complete: function(response) { - $obj.prop('disabled', false); - } + var add_fav_icon = $obj.attr('data-add-fav-icon') || "fa-heart-o"; + var remove_fav_icon = $obj.attr('data-remove-fav-icon') || "fa-heart"; + + var add_fav_title = $obj.attr('data-add-fav-title') || "Add to Favourites"; + var remove_fav_title = $obj.attr('data-remove-fav-title') || "Remove from Favourites"; + + $obj.prop('disabled', true); + $.ajax({ + url: $obj.attr('href'), + type: 'POST', + data: { + target_model: $obj.attr('model'), + target_object_id: target_id + }, + success: function (response) { + if (response.status == 'added') { + $obj.children().removeClass(add_fav_icon).addClass(remove_fav_icon); + if ($obj.attr('title') && $obj.attr('title') === add_fav_title) { + $obj.attr('title', remove_fav_title); + } + } + else { + $obj.children().removeClass(remove_fav_icon).addClass(add_fav_icon); + if ($obj.attr('title') && $obj.attr('title') === remove_fav_title) { + $obj.attr('title', add_fav_title); + } + } + //$obj.parent('.favit').children('.fav-count').text(response.fav_count); + $obj.prop('disabled', false); + } + }); + }); + + $('.btn.unfave').click(function () { + var $obj = $(this); + $obj.prop('disabled', true); + $.ajax({ + url: $obj.attr('href'), + type: 'POST', + data: { + target_model: $obj.data('model'), + target_object_id: $obj.data('id') + }, + success: function (response) { + if (response.status == 'deleted') { + $obj.parent().remove(); + } + }, + complete: function (response) { + $obj.prop('disabled', false); + } + }); }); - }); }); diff --git a/favit/templates/favit/button.html b/favit/templates/favit/button.html index 4ddffe4..d2b7062 100644 --- a/favit/templates/favit/button.html +++ b/favit/templates/favit/button.html @@ -1,6 +1,6 @@
{{ fav_count }}
diff --git a/favit/templatetags/favit_tags.py b/favit/templatetags/favit_tags.py index aa773d6..66ace57 100644 --- a/favit/templatetags/favit_tags.py +++ b/favit/templatetags/favit_tags.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- + from django import template from django.template.loader import render_to_string - +from classytags.utils import flatten_context from ..models import Favorite @@ -13,7 +13,7 @@ def favorite_button(context, target): user = context['request'].user # do nothing when user isn't authenticated - if not user.is_authenticated(): + if not user.is_authenticated: return '' target_model = '.'.join((target._meta.app_label, target._meta.object_name)) @@ -24,14 +24,15 @@ def favorite_button(context, target): if Favorite.objects.get_favorite(user, target): undo = True - return render_to_string( - 'favit/button.html', { - 'target_model': target_model, - 'target_object_id': target.id, - 'undo': undo, - 'fav_count': Favorite.objects.for_object(target).count() - } - ) + context = flatten_context(context) + context.update({ + 'target_model': target_model, + 'target_object_id': target.id, + 'undo': undo, + 'fav_count': Favorite.objects.for_object(target).count() + }) + + return render_to_string('favit/button.html', context) @register.simple_tag(takes_context=True) @@ -39,7 +40,7 @@ def unfave_button(context, target): user = context['request'].user # do nothing when user isn't authenticated - if not user.is_authenticated(): + if not user.is_authenticated: return '' if Favorite.objects.get_favorite(user, target) is None: @@ -47,12 +48,12 @@ def unfave_button(context, target): target_model = '.'.join((target._meta.app_label, target._meta.object_name)) - return render_to_string( - 'favit/unfave-button.html', { - 'target_model': target_model, - 'target_object_id': target.id, - } - ) + context = flatten_context(context) + context.update({ + 'target_model': target_model, + 'target_object_id': target.id, + }) + return render_to_string('favit/unfave-button.html', context) @register.filter @@ -82,7 +83,7 @@ def favorites_count(obj): return Favorite.objects.for_object(obj).count() -@register.assignment_tag +@register.simple_tag def user_favorites(user, app_model=None): """ Usage: @@ -107,7 +108,7 @@ def user_favorites(user, app_model=None): return Favorite.objects.for_user(user, app_model) -@register.assignment_tag +@register.simple_tag def model_favorites(app_model): """ Gets all favorited objects that are instances of a model diff --git a/favit/urls.py b/favit/urls.py index 8568fc6..1d22b39 100644 --- a/favit/urls.py +++ b/favit/urls.py @@ -2,14 +2,7 @@ from favit.views import add_or_remove, remove -try: - urlpatterns = patterns('favit.views', - url(r'^add-or-remove$', 'add_or_remove', name='favit-add-or-remove'), - url(r'^remove$', 'remove', name='favit-remove'), - ) -except NameError: - urlpatterns = [ - url(r'^add-or-remove$', add_or_remove), - url(r'^remove$', remove), - ] - +urlpatterns = [ + url(r'^add-or-remove$', add_or_remove, name='favit.views.add_or_remove'), + url(r'^remove$', remove, name='favit.views.remove'), +] diff --git a/favit/views.py b/favit/views.py index 90043b4..8b78aba 100644 --- a/favit/views.py +++ b/favit/views.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- + import json from django.contrib.auth.decorators import login_required @@ -11,7 +11,7 @@ def add_or_remove(request): if not request.is_ajax(): - return HttpResponseNotAllowed() + return HttpResponseNotAllowed([]) user = request.user @@ -37,7 +37,7 @@ def add_or_remove(request): return HttpResponse( json.dumps(response, ensure_ascii=False), - mimetype='application/json' + content_type='application/json' ) @@ -45,7 +45,7 @@ def add_or_remove(request): def remove(request): if not request.is_ajax(): - return HttpResponseNotAllowed() + return HttpResponseNotAllowed([]) user = request.user diff --git a/setup.py b/setup.py index 591b876..21ee1c6 100755 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setup( name='django-favit', - version='0.4.0', + version='0.5.1', packages=find_packages(), include_package_data=True, license='MIT License', @@ -19,12 +19,17 @@ classifiers=[ 'Environment :: Web Environment', 'Framework :: Django', + 'Framework :: Django :: 1.11', + 'Framework :: Django :: 2.2', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ],