diff --git a/bookshare/core/models.py b/bookshare/core/models.py index db6655c247937a6c8163c687bfc391d717a396de..eb30b8ec953def9859295acc4f7fcd5c7a779259 100644 --- a/bookshare/core/models.py +++ b/bookshare/core/models.py @@ -14,6 +14,8 @@ class Student(TimeStampedModel): slug = AutoSlugField(populate_from='user', unique=True) + emails_sent = models.PositiveIntegerField(default=0) + def get_absolute_url(self): return reverse('profile', kwargs={'slug': self.slug}) diff --git a/bookshare/core/tests.py b/bookshare/core/tests.py index 7ce503c2dd97ba78597f6ff6e4393132753573f6..4699e3f15d6b3abc939093aadc73c749bcf04c8e 100644 --- a/bookshare/core/tests.py +++ b/bookshare/core/tests.py @@ -1,3 +1,19 @@ from django.test import TestCase +from django.http import HttpRequest +from django.core.urlresolvers import resolve +from .models import Student +from .views import DetailStudent, StudentRatings + +class ProfileTest(TestCase): + + def test_username_resolves_to_profile(self): + found = resolve('/student/username/') + self.assertEqual(found.func, home_page) + + request = HttpResponse() + reponse = profile(request) + + self.assertTrue(response.content.startswith('')) + self.assertIn('user.get_full_name', response.content) + self.assertTrue(response.content.endswith('')) -# Create your tests here. diff --git a/bookshare/mod/__init__.py b/bookshare/mod/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bookshare/mod/admin.py b/bookshare/mod/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /dev/null +++ b/bookshare/mod/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/bookshare/mod/models.py b/bookshare/mod/models.py new file mode 100644 index 0000000000000000000000000000000000000000..71a836239075aa6e6e4ecb700e9c42c95c022d91 --- /dev/null +++ b/bookshare/mod/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/bookshare/mod/templates/email_ratio_mod.html b/bookshare/mod/templates/email_ratio_mod.html new file mode 100644 index 0000000000000000000000000000000000000000..73b5532029c13f0b36013efe7f841e053c96050b --- /dev/null +++ b/bookshare/mod/templates/email_ratio_mod.html @@ -0,0 +1,52 @@ +{% extends 'layouts/base.html' %} + +{% block title %} +SRCT Bookshare • Mod +{% endblock title %} + +{% block content %} + +
+
+

Ratio Emails Sent to Listings Posted

+
+
+ +
+
+ + + + + + + + + + {% for student in email_happy_students %} + + + + {% widthratio student.emails_sent student.num_books 1 as ratio %} + + + + + {% empty %} + Nothing Here! + {% endfor %} + +

Emails Sent

Listings Posted

Ratio

Student

Action

{{ student.emails_sent }}{{ student.num_books }}{{ ratio }} + {{ student.user.get_full_name }} ({{ student.user.username }}) + + + Email + + + Make Inactive + +
+
+
+ +{% endblock content %} diff --git a/bookshare/templates/mod.html b/bookshare/mod/templates/flag_mod.html similarity index 96% rename from bookshare/templates/mod.html rename to bookshare/mod/templates/flag_mod.html index 91bb701a355b46d2f8bda7e1bb455fbc774a263a..d40374de8e8fb2c9f559af72ee2d540e492c7514 100644 --- a/bookshare/templates/mod.html +++ b/bookshare/mod/templates/flag_mod.html @@ -35,8 +35,6 @@ SRCT Bookshare • Mod Delete - {% empty %} - Nothing Here! {% endfor %} diff --git a/bookshare/mod/templates/listing_num_mod.html b/bookshare/mod/templates/listing_num_mod.html new file mode 100644 index 0000000000000000000000000000000000000000..d75488b3eccd2d59b92fd0c5a81bc66115295899 --- /dev/null +++ b/bookshare/mod/templates/listing_num_mod.html @@ -0,0 +1,45 @@ +{% extends 'layouts/base.html' %} + +{% block title %} +SRCT Bookshare • Mod +{% endblock title %} + +{% block content %} + +
+
+

Users by number of Listings

+
+
+ +
+
+ + + + + + + + {% for student in students %} + + + + + + {% endfor %} + +

Number

Student

Action

{{ student.num_books }} + {{ student.user.get_full_name }} ({{ student.user.username }}) + + + Email + + + Make Inactive + +
+
+
+ +{% endblock content %} diff --git a/bookshare/mod/templates/mod.html b/bookshare/mod/templates/mod.html new file mode 100644 index 0000000000000000000000000000000000000000..b69b719821db7a53ef33d06c04a48b36f2d1efd8 --- /dev/null +++ b/bookshare/mod/templates/mod.html @@ -0,0 +1,35 @@ +{% extends 'layouts/base.html' %} + +{% block title %} +SRCT Bookshare • Mod +{% endblock title %} + +{% block content %} + +
+
+

Moderation

+
+
+ +
+
+ + Moderate by... + +
+ Flags +
+ +
+ Listings +
+ +
+ Emails +
+ +
+
+ +{% endblock content %} diff --git a/bookshare/mod/tests.py b/bookshare/mod/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/bookshare/mod/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/bookshare/mod/urls.py b/bookshare/mod/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..d2b2055e8f6ff81a529d6d05f9ea19d09d1c71b3 --- /dev/null +++ b/bookshare/mod/urls.py @@ -0,0 +1,17 @@ +# core django imports +from django.conf.urls import patterns, url +from django.views.decorators.cache import cache_page +# imports from your apps +from .views import ModLandingView, FlagModView, ListingNumModView,\ + UserEmailRatioModView + +urlpatterns = patterns('', + url(r'^$', cache_page(60 * 15)(ModLandingView.as_view()), name='mod_page'), + + url(r'^flags/$', FlagModView.as_view(), name='flag_mod'), + + url(r'^listing-nums/$', ListingNumModView.as_view(), name='listing_nums'), + + url(r'^email-ratio/$', UserEmailRatioModView.as_view(), name='email_ratio'), + +) diff --git a/bookshare/mod/views.py b/bookshare/mod/views.py new file mode 100644 index 0000000000000000000000000000000000000000..900526f38f71c0d7fb5fe47232458b1f8a895772 --- /dev/null +++ b/bookshare/mod/views.py @@ -0,0 +1,38 @@ +# core django imports +from django.views.generic import TemplateView, ListView +from django.db.models import Count +# third party imports +from braces.views import LoginRequiredMixin, SuperuserRequiredMixin +# imports from your apps +from trades.models import Listing +from core.models import Student + + +class ModLandingView(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView): + template_name = 'mod.html' + + login_url = 'login' + +class FlagModView(LoginRequiredMixin, SuperuserRequiredMixin, ListView): + queryset = Listing.objects.annotate(num_flags=Count('flag')).order_by('-num_flags')[:20] + context_object_name = 'listings' + template_name = 'flag_mod.html' + login_url = 'login' + +class ListingNumModView(LoginRequiredMixin, SuperuserRequiredMixin, ListView): + queryset = Student.objects.annotate(num_books=Count('listing')).order_by('-num_books')[:20] + context_object_name = 'students' + template_name = 'listing_num_mod.html' + login_url = 'login' + +class UserEmailRatioModView(LoginRequiredMixin, SuperuserRequiredMixin, TemplateView): + template_name = 'email_ratio_mod.html' + + def get_context_data(self, **kwargs): + context = super(UserEmailRatioModView, self).get_context_data(**kwargs) + + students_by_emails = Student.objects.order_by('-emails_sent')[:20] + students_listings = students_by_emails.annotate(num_books=Count('listing')) + + context['email_happy_students'] = students_listings + return context diff --git a/bookshare/settings/settings.py b/bookshare/settings/settings.py index 0f0fe3013544160dbebbdb1741f8b7dcda6c30fc..d58c87f60cf868be1d078c32de3b582060f1d4f9 100644 --- a/bookshare/settings/settings.py +++ b/bookshare/settings/settings.py @@ -37,6 +37,7 @@ INSTALLED_APPS = ( 'trades', 'core', 'lookouts', + 'mod', # packages 'randomslugfield', 'django_gravatar', diff --git a/bookshare/settings/urls.py b/bookshare/settings/urls.py index f6ed2e4264043665226a5ee2901620eba56488a0..1e03030adc16b2fd113d140535d2d8f43e2813f6 100644 --- a/bookshare/settings/urls.py +++ b/bookshare/settings/urls.py @@ -6,7 +6,7 @@ from django.conf import settings from django.contrib import admin from django.views.decorators.cache import cache_page # imports from your apps -from .views import HomepageView, ChartsView, ModView +from .views import HomepageView, ChartsView admin.autodiscover() @@ -20,6 +20,7 @@ urlpatterns = patterns('', url(r'^share/', include('trades.urls')), url(r'^student/', include('core.urls')), url(r'^lookouts/', include('lookouts.urls')), + url(r'^mod/', include('mod.urls')), # search url(r'^search/', include('haystack.urls'), name='search'), @@ -30,8 +31,6 @@ urlpatterns = patterns('', url(r'^$', HomepageView.as_view(), name='homepage'), url(r'^charts/?$', cache_page(60 * 10)(ChartsView.as_view()), name='charts'), - url(r'^mod/?$', ModView.as_view(), name='mod'), - # static pages url(r'^about/?$', cache_page(60 * 15)(TemplateView.as_view(template_name='about.html')), diff --git a/bookshare/settings/views.py b/bookshare/settings/views.py index ea996f93de0360fb615e0897ea06769da08af331..1c2a41a860a413af16203d4b98a856a507120aeb 100644 --- a/bookshare/settings/views.py +++ b/bookshare/settings/views.py @@ -1,10 +1,10 @@ # standard library imports from collections import Counter # core django imports -from django.views.generic import TemplateView, ListView +from django.views.generic import TemplateView from django.db.models import Sum, Count # third party imports -from braces.views import LoginRequiredMixin, SuperuserRequiredMixin +from braces.views import LoginRequiredMixin # imports from your apps from lookouts.models import Lookout from trades.models import Listing, Bid @@ -55,9 +55,3 @@ class ChartsView(LoginRequiredMixin, TemplateView): context['total_students'] = Student.objects.count() context['total_proceeds'] = total_proceeds return context - -class ModView(LoginRequiredMixin, SuperuserRequiredMixin, ListView): - queryset = Listing.objects.annotate(num_flags=Count('flag')).order_by('-num_flags')[:20] - context_object_name = 'listings' - template_name = 'mod.html' - login_url = 'login' diff --git a/bookshare/trades/models.py b/bookshare/trades/models.py index 62cad9278e4eb6eb016e8a222ba3dbbf57e443d7..56abc671c48965184c049873e6fdbc9ee01535d7 100644 --- a/bookshare/trades/models.py +++ b/bookshare/trades/models.py @@ -47,8 +47,10 @@ class Listing(TimeStampedModel): title = models.CharField(max_length=200) author = models.CharField(max_length=200) isbn = models.CharField(max_length=20, - validators=[RegexValidator('^[0-9xX-]{10,20}', - message='Please enter a valid ISBN.')]) + validators=[RegexValidator( + regex='^[0-9xX-]{10,20}', + message='Please enter a valid ISBN.', + code='invalid_isbn')]) year = models.IntegerField(null=True, blank=True, # some professors may assign books still to be officially published validators=[MaxValueValidator(date.today().year+1)]) @@ -61,8 +63,10 @@ class Listing(TimeStampedModel): access_code = models.CharField(choices=ACCESS_CODE_CHOICES, max_length=30, default=NOT_APPLICABLE) course_abbr = models.CharField(max_length=10, blank=True, - validators=[RegexValidator('^([a-zA-Z]){2,4} (\d){3}$', - message='Please enter a valid course.')]) + validators=[RegexValidator( + regex='^([a-zA-Z]){2,4} (\d){3}$', + message='Please enter a valid course.', + code='invalid_course_abbr')]) description = models.TextField(blank=True, max_length=2000) price = models.PositiveIntegerField(default=0, validators=[MaxValueValidator(1000)]) diff --git a/bookshare/trades/views.py b/bookshare/trades/views.py index 6884a3be6f6ee7ceeb272acf4b707451ff16970b..7466b6d52fed8ff728aaa44fdb0ce3e830d3baf9 100644 --- a/bookshare/trades/views.py +++ b/bookshare/trades/views.py @@ -403,6 +403,9 @@ class ExchangeListing(LoginRequiredMixin, FormValidMessageMixin, UpdateView): msg.attach_alternative(html_content, "text/html") msg.send() + self.obj.poster.emails_sent += 2 + self.obj.poster.save() + return super(ExchangeListing, self).form_valid(form) def get_context_data(self, **kwargs): @@ -468,6 +471,9 @@ class UnExchangeListing(LoginRequiredMixin, FormValidMessageMixin, UpdateView): msg.attach_alternative(html_content, "text/html") msg.send() + self.obj.poster.emails_sent += 2 + self.obj.poster.save() + # this has to come after the email has been sent, otherwise these are # cleaned out form.instance.exchanged = False