Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Rewrite django-announcements #2

Merged
merged 3 commits into from

1 participant

@paltman
Owner

No description provided.

@paltman
Owner

I have released this version to dist.pinaxproject.com/dev/ as 0.2.dev1

paltman added some commits
@paltman paltman Add crud views with pluggable view mixin
- idea for this is to quickly standup a staff
  admin for announcements and provide your own
  authentication rules via the mixin.
4546b7e
@paltman paltman 0.2.1 c24201d
@paltman paltman merged commit aa2b4fe into master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Nov 11, 2011
  1. @paltman

    Rewrite django-announcements

    paltman authored
Commits on Nov 29, 2011
  1. @paltman

    Add crud views with pluggable view mixin

    paltman authored
    - idea for this is to quickly standup a staff
      admin for announcements and provide your own
      authentication rules via the mixin.
  2. @paltman

    0.2.1

    paltman authored
This page is out of date. Refresh to see the latest.
View
10 announcements/__init__.py
@@ -1,9 +1 @@
-VERSION = (0, 1, 0, "final")
-
-def get_version():
- if VERSION[3] != "final":
- return "%s.%s.%s%s" % (VERSION[0], VERSION[1], VERSION[2], VERSION[3])
- else:
- return "%s.%s.%s" % (VERSION[0], VERSION[1], VERSION[2])
-
-__version__ = get_version()
+__version__ = "0.2.1"
View
11 announcements/admin.py
@@ -1,20 +1,14 @@
from django.contrib import admin
-from announcements.models import Announcement
-from announcements.forms import AnnouncementAdminForm
+from announcements.models import Announcement, Dismissal
class AnnouncementAdmin(admin.ModelAdmin):
list_display = ("title", "creator", "creation_date", "members_only")
list_filter = ("members_only",)
- form = AnnouncementAdminForm
fieldsets = [
(None, {
- "fields": ["title", "content", "site_wide", "members_only"],
- }),
-
- ("Manage announcement", {
- "fields": ["send_now"],
+ "fields": ["title", "content", "site_wide", "members_only", "publish_start", "publish_end", "dismissal_type"],
}),
]
@@ -26,3 +20,4 @@ def save_model(self, request, obj, form, change):
admin.site.register(Announcement, AnnouncementAdmin)
+admin.site.register(Dismissal)
View
9 announcements/context_processors.py
@@ -1,9 +0,0 @@
-from announcements.models import current_announcements_for_request
-
-
-def site_wide_announcements(request):
- """
- Adds the site-wide announcements to the global context of templates.
- """
- ctx = {"site_wide_announcements": current_announcements_for_request(request, site_wide=True)}
- return ctx
View
14 announcements/feeds.py
@@ -1,3 +1,7 @@
+import datetime
+
+from django.db.models import Q
+
from atomformat import Feed
from announcements.models import Announcement
@@ -16,7 +20,15 @@ class AnnouncementsBase(Feed):
# def item_links
def items(self):
- return Announcement.objects.order_by("-creation_date")[:10]
+ return Announcement.objects.filter(
+ publish_start__lte=datetime.datetime.now()
+ ).filter(
+ Q(publish_end__isnull=True)|Q(publish_end__gt=datetime.datetime.now())
+ ).filter(
+ site_wide=True
+ ).exclude(
+ members_only=True
+ ).order_by("-creation_date")[:10]
def item_title(self, item):
return item.title
View
42 announcements/forms.py
@@ -1,40 +1,18 @@
from django import forms
-from django.contrib.auth.models import User
-from django.utils.translation import ugettext_lazy as _
-
-try:
- from notification import models as notification
-except ImportError:
- notification = None
from announcements.models import Announcement
-class AnnouncementAdminForm(forms.ModelForm):
- """
- A custom form for the admin of the Announcement model. Has an extra field
- called send_now that when checked will send out the announcement allowing
- the user to decide when that happens.
- """
-
- send_now = forms.BooleanField(required=False,
- help_text=_("Tick this box to send out this announcement now."))
+class AnnouncementForm(forms.ModelForm):
class Meta:
model = Announcement
- exclude = ("creator", "creation_date")
-
- def save(self, commit=True):
- """
- Checks the send_now field in the form and when True sends out the
- announcement through notification if present.
- """
-
- announcement = super(AnnouncementAdminForm, self).save(commit)
- if self.cleaned_data["send_now"]:
- if notification:
- users = User.objects.all()
- notification.send(users, "announcement", {
- "announcement": announcement,
- }, on_site=False, queue=True)
- return announcement
+ fields = [
+ "title",
+ "content",
+ "site_wide",
+ "members_only",
+ "dismissal_type",
+ "publish_start",
+ "publish_end"
+ ]
View
16 announcements/management.py
@@ -1,16 +0,0 @@
-from django.db.models import get_models, signals
-
-
-try:
- from notification import models as notification
-
- def create_notice_types(app, created_models, verbosity, **kwargs):
- """
- Create the announcement notice type for sending notifications when
- announcements occur.
- """
- notification.create_notice_type("announcement", "Announcement", "you have received an announcement")
-
- signals.post_syncdb.connect(create_notice_types, sender=notification)
-except ImportError:
- print "Skipping creation of NoticeTypes as notification app not found"
View
30 announcements/mixins.py
@@ -0,0 +1,30 @@
+from django.conf import settings
+from django.utils.importlib import import_module
+
+
+def _resolve(mixin_setting):
+ if isinstance(mixin_setting, basestring):
+ try:
+ mod_name, klass_name = mixin_setting.rsplit(".", 1)
+ except ValueError:
+ raise Exception("Improperly configured.")
+ try:
+ mod = import_module(mod_name)
+ except ImportError:
+ raise Exception("Could not import %s" % mod_name)
+ try:
+ klass = getattr(mod, klass_name)
+ except AttributeError:
+ raise Exception("The module '%s' does not contain '%s'." % (mod_name, klass_name))
+ mixin_setting = klass
+ return mixin_setting
+
+
+class DefaultProtectedMixin(object):
+
+ pass
+
+
+ProtectedMixin = _resolve(
+ getattr(settings, "ANNOUNCEMENTS_PROTECTED_MIXIN", DefaultProtectedMixin)
+)
View
83 announcements/models.py
@@ -1,63 +1,41 @@
-from datetime import datetime
+import datetime
from django.db import models
-from django.conf import settings
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
-try:
- set
-except NameError:
- from sets import Set as set # Python 2.3 fallback
-
-
-class AnnouncementManager(models.Manager):
- """
- A basic manager for dealing with announcements.
- """
- def current(self, exclude=[], site_wide=False, for_members=False):
- """
- Fetches and returns a queryset with the current announcements. This
- method takes the following parameters:
-
- ``exclude``
- A list of IDs that should be excluded from the queryset.
-
- ``site_wide``
- A boolean flag to filter to just site wide announcements.
-
- ``for_members``
- A boolean flag to allow member only announcements to be returned
- in addition to any others.
- """
- queryset = self.all()
- if site_wide:
- queryset = queryset.filter(site_wide=True)
- if exclude:
- queryset = queryset.exclude(pk__in=exclude)
- if not for_members:
- queryset = queryset.filter(members_only=False)
- queryset = queryset.order_by("-creation_date")
- return queryset
-
class Announcement(models.Model):
"""
A single announcement.
"""
+ DISMISSAL_NO = 1
+ DISMISSAL_SESSION = 2
+ DISMISSAL_PERMANENT = 3
+
+ DISMISSAL_CHOICES = [
+ (DISMISSAL_NO, "No Dismissals Allowed"),
+ (DISMISSAL_SESSION, "Session Only Dismissal"),
+ (DISMISSAL_PERMANENT, "Permanent Dismissal Allowed")
+ ]
+
title = models.CharField(_("title"), max_length=50)
content = models.TextField(_("content"))
creator = models.ForeignKey(User, verbose_name=_("creator"))
- creation_date = models.DateTimeField(_("creation_date"), default=datetime.now)
+ creation_date = models.DateTimeField(_("creation_date"), default=datetime.datetime.now)
site_wide = models.BooleanField(_("site wide"), default=False)
members_only = models.BooleanField(_("members only"), default=False)
-
- objects = AnnouncementManager()
+ dismissal_type = models.IntegerField(choices=DISMISSAL_CHOICES, default=DISMISSAL_SESSION)
+ publish_start = models.DateTimeField(_("publish_start"), default=datetime.datetime.now)
+ publish_end = models.DateTimeField(_("publish_end"), blank=True, null=True)
def get_absolute_url(self):
- return ("announcement_detail", [str(self.pk)])
- get_absolute_url = models.permalink(get_absolute_url)
+ return reverse("announcement_detail", args=[self.pk])
+
+ def dismiss_url(self):
+ if self.dismissal_type != Announcement.DISMISSAL_NO:
+ return reverse("announcement_dismiss", args=[self.pk])
def __unicode__(self):
return self.title
@@ -67,20 +45,9 @@ class Meta:
verbose_name_plural = _("announcements")
-def current_announcements_for_request(request, **kwargs):
- """
- A helper function to get the current announcements based on some data from
- the HttpRequest.
+class Dismissal(models.Model):
- If request.user is authenticated then allow the member only announcements
- to be returned.
-
- Exclude announcements that have already been viewed by the user based on
- the ``excluded_announcements`` session variable.
- """
- defaults = {}
- if request.user.is_authenticated():
- defaults["for_members"] = True
- defaults["exclude"] = request.session.get("excluded_announcements", set())
- defaults.update(kwargs)
- return Announcement.objects.current(**defaults)
+ user = models.ForeignKey(User, related_name="announcement_dismissals")
+ announcement = models.ForeignKey(Announcement, related_name="dismissals")
+ dismissed_at = models.DateTimeField(default=datetime.datetime.now)
+
View
6 announcements/signals.py
@@ -0,0 +1,6 @@
+import django.dispatch
+
+
+announcement_created = django.dispatch.Signal(providing_args=["announcement", "request"])
+announcement_updated = django.dispatch.Signal(providing_args=["announcement", "request"])
+announcement_deleted = django.dispatch.Signal(providing_args=["announcement", "request"])
View
3  announcements/templates/notification/announcement/full.txt
@@ -1,3 +0,0 @@
-{{ announcement.title }}
-
-{{ announcement.content }}
View
36 announcements/templatetags/announcement_tags.py
@@ -1,36 +0,0 @@
-from django.template import Library, Node
-
-from announcements.models import current_announcements_for_request
-
-
-register = Library()
-
-
-class FetchAnnouncementsNode(Node):
- def __init__(self, context_var, limit=None):
- self.context_var = context_var
- self.limit = limit
-
- def render(self, context):
- try:
- request = context["request"]
- except KeyError:
- raise Exception("{% fetch_announcements %} requires the HttpRequest in context.")
- kwargs = {}
- announcements = current_announcements_for_request(request, **kwargs)
- if self.limit:
- announcements = announcements[:self.limit]
- context[self.context_var] = announcements
- return ""
-
-@register.tag
-def fetch_announcements(parser, token):
- bits = token.split_contents()
- # @@@ very naive parsing
- if len(bits) == 5:
- limit = bits[2]
- context_var = bits[4]
- elif len(bits) == 3:
- limit = None
- context_var = bits[2]
- return FetchAnnouncementsNode(context_var, limit)
View
52 announcements/templatetags/announcements_tags.py
@@ -0,0 +1,52 @@
+import datetime
+
+from django import template
+from django.db.models import Q
+
+from announcements.models import Announcement
+
+
+register = template.Library()
+
+
+class AnnouncementsNode(template.Node):
+
+ @classmethod
+ def handle_token(cls, parser, token):
+ bits = token.split_contents()
+ if len(bits) != 3:
+ raise template.TemplateSyntaxError
+ return cls(as_var = bits[2])
+
+ def __init__(self, as_var):
+ self.as_var = as_var
+
+ def render(self, context):
+ request = context["request"]
+ qs = Announcement.objects.filter(
+ publish_start__lte=datetime.datetime.now()
+ ).filter(
+ Q(publish_end__isnull=True)|Q(publish_end__gt=datetime.datetime.now())
+ ).filter(
+ site_wide=True
+ )
+
+ exclusions = request.session.get("excluded_announcements", set())
+ if request.user.is_authenticated():
+ for dismissal in request.user.announcement_dismissals.all():
+ exclusions.add(dismissal.announcement.pk)
+ else:
+ qs = qs.exclude(members_only=True)
+ context[self.as_var] = qs.exclude(pk__in=exclusions)
+ return ""
+
+
+@register.tag
+def announcements(parser, token):
+ """
+ Usage::
+ {% announcements as var %}
+
+ Returns a list of announcements
+ """
+ return AnnouncementsNode.handle_token(parser, token)
View
35 announcements/tests.py
@@ -1,35 +0,0 @@
-__test__ = {"ANNOUNCEMENT_TESTS": r"""
->>> from django.contrib.auth.models import User
->>> from announcements.models import Announcement
-
-# create ourselves a user to associate to the announcements
->>> superuser = User.objects.create_user("brosner", "brosner@gmail.com")
-
->>> a1 = Announcement.objects.create(title="Down for Maintenance", creator=superuser)
->>> a2 = Announcement.objects.create(title="Down for Maintenance Again", creator=superuser)
->>> a3 = Announcement.objects.create(title="Down for Maintenance Again And Again", creator=superuser, site_wide=True)
->>> a4 = Announcement.objects.create(title="Members Need to Fill Out New Profile Info", creator=superuser, members_only=True)
->>> a5 = Announcement.objects.create(title="Expected Down Time", creator=superuser, members_only=True, site_wide=True)
-
-# get the announcements that are publically viewable. this is the same as
-# calling as using site_wide=False, for_members=False
->>> Announcement.objects.current()
-[<Announcement: Down for Maintenance Again And Again>, <Announcement: Down for Maintenance Again>, <Announcement: Down for Maintenance>]
-
-# get just the publically viewable site wide announcements
->>> Announcement.objects.current(site_wide=True)
-[<Announcement: Down for Maintenance Again And Again>]
-
-# get the announcements that authenticated users can see.
->>> Announcement.objects.current(for_members=True)
-[<Announcement: Expected Down Time>, <Announcement: Members Need to Fill Out New Profile Info>, <Announcement: Down for Maintenance Again And Again>, <Announcement: Down for Maintenance Again>, <Announcement: Down for Maintenance>]
-
-# get just site wide announcements that authenticated users can see.
->>> Announcement.objects.current(site_wide=True, for_members=True)
-[<Announcement: Expected Down Time>, <Announcement: Down for Maintenance Again And Again>]
-
-# exclude a couple of announcements from the publically viewabled messages.
->>> Announcement.objects.current(exclude=[a1.pk, a5.pk])
-[<Announcement: Down for Maintenance Again And Again>, <Announcement: Down for Maintenance Again>]
-
-"""}
View
23 announcements/urls.py
@@ -1,18 +1,15 @@
from django.conf.urls.defaults import *
-from django.views.generic import list_detail
-from announcements.models import Announcement
-from announcements.views import *
-
-
-announcement_detail_info = {
- "queryset": Announcement.objects.all(),
-}
+from announcements.views import detail, dismiss
+from announcements.views import CreateAnnouncementView, UpdateAnnouncementView
+from announcements.views import DeleteAnnouncementView, AnnouncementListView
urlpatterns = patterns("",
- url(r"^(?P<object_id>\d+)/$", list_detail.object_detail,
- announcement_detail_info, name="announcement_detail"),
- url(r"^(?P<object_id>\d+)/hide/$", announcement_hide,
- name="announcement_hide"),
- url(r"^$", announcement_list, name="announcement_home"),
+ url(r"announcement/(?P<pk>\d+)/$", detail, name="announcements_detail"),
+ url(r"announcement/(?P<pk>\d+)/hide/$", dismiss, name="announcement_dismiss"),
+
+ url(r"announcement/create/$", CreateAnnouncementView.as_view(), name="announcements_create"),
+ url(r"announcement/(?P<pk>\d+)/update/$", UpdateAnnouncementView.as_view(), name="announcements_update"),
+ url(r"announcement/(?P<pk>\d+)/delete/$", DeleteAnnouncementView.as_view(), name="announcements_delete"),
+ url(r"", AnnouncementListView.as_view(), name="announcements_list"),
)
View
121 announcements/views.py
@@ -1,36 +1,101 @@
-from django.http import HttpResponseRedirect
-from django.views.generic import list_detail
+from django.core.urlresolvers import reverse
+from django.http import HttpResponse, HttpResponseNotAllowed
from django.shortcuts import get_object_or_404
+from django.template.response import TemplateResponse
+from django.views.generic.edit import CreateView, UpdateView, DeleteView
+from django.views.generic.list import ListView
-from announcements.models import Announcement, current_announcements_for_request
+from announcements.forms import AnnouncementForm
+from announcements.mixins import ProtectedMixin
+from announcements.models import Announcement
+from announcements.signals import announcement_created, announcement_updated, announcement_deleted
-try:
- set
-except NameError:
- from sets import Set as set # Python 2.3 fallback
+def dismiss(request, pk):
+ if request.method != "POST":
+ return HttpResponseNotAllowed(["POST"])
+
+ announcement = get_object_or_404(Announcement, pk=pk)
+
+ if announcement.dismissal_type == Announcement.DISMISSAL_SESSION:
+ excluded = request.session.get("excluded_announcements", set())
+ excluded.add(announcement.pk)
+ request.session["excluded_announcements"] = excluded
+ status = 200
+ elif announcement.dismissal_type == Announcement.DISMISSAL_PERMANENT and request.user.is_authenticated():
+ announcement.dismissals.create(user=request.user)
+ status = 200
+ else:
+ status = 409
+
+ return HttpResponse(status=status)
-def announcement_list(request):
- """
- A basic view that wraps ``django.views.list_detail.object_list`` and
- uses ``current_announcements_for_request`` to get the current
- announcements.
- """
- queryset = current_announcements_for_request(request)
- return list_detail.object_list(request, **{
- "queryset": queryset,
- "allow_empty": True,
+
+def detail(request, pk):
+ announcement = get_object_or_404(Announcement, pk=pk)
+ return TemplateResponse(request, "announcements/detail.html", {
+ "announcement": announcement
})
-def announcement_hide(request, object_id):
- """
- Mark this announcement hidden in the session for the user.
- """
- announcement = get_object_or_404(Announcement, pk=object_id)
- # TODO: perform some basic security checks here to ensure next is not bad
- redirect_to = request.GET.get("next")
- excluded_announcements = request.session.get("excluded_announcements", set())
- excluded_announcements.add(announcement.pk)
- request.session["excluded_announcements"] = excluded_announcements
- return HttpResponseRedirect(redirect_to)
+class CreateAnnouncementView(CreateView, ProtectedMixin):
+
+ model = Announcement
+ form_class = AnnouncementForm
+
+ def form_valid(self, form):
+ self.object = form.save(commit=False)
+ self.object.creator = self.request.user
+ self.object.save()
+
+ announcement_created.send(
+ sender=self.object,
+ announcement=self.object,
+ request=self.request
+ )
+ return super(CreateAnnouncementView, self).form_valid(form)
+
+ def get_success_url(self):
+ return reverse("announcements_list")
+
+
+class UpdateAnnouncementView(UpdateView, ProtectedMixin):
+
+ model = Announcement
+ form_class = AnnouncementForm
+
+ def form_valid(self, form):
+ response = super(UpdateAnnouncementView, self).form_valid(form)
+ announcement_updated.send(
+ sender=self.object,
+ announcement=self.object,
+ request=self.request
+ )
+ return response
+
+ def get_success_url(self):
+ return reverse("announcements_list")
+
+
+class DeleteAnnouncementView(DeleteView, ProtectedMixin):
+
+ model = Announcement
+
+ def form_valid(self, form):
+ response = super(DeleteAnnouncementView, self).form_valid(form)
+ announcement_deleted.send(
+ sender=self.object,
+ announcement=self.object,
+ request=self.request
+ )
+ return response
+
+ def get_success_url(self):
+ return reverse("announcements_list")
+
+
+class AnnouncementListView(ListView, ProtectedMixin):
+
+ model = Announcement
+ queryset = Announcement.objects.all().order_by("-creation_date")
+ paginate_by = 50
View
131 docs/Makefile
@@ -0,0 +1,131 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+PAPER =
+BUILDDIR = _build
+
+# Internal variables.
+PAPEROPT_a4 = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+PROJECT = django-announcements
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+help:
+ @echo "Please use \`make <target>' where <target> is one of"
+ @echo " html to make standalone HTML files"
+ @echo " dirhtml to make HTML files named index.html in directories"
+ @echo " singlehtml to make a single large HTML file"
+ @echo " pickle to make pickle files"
+ @echo " json to make JSON files"
+ @echo " htmlhelp to make HTML files and a HTML help project"
+ @echo " qthelp to make HTML files and a qthelp project"
+ @echo " devhelp to make HTML files and a Devhelp project"
+ @echo " epub to make an epub"
+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
+ @echo " text to make text files"
+ @echo " man to make manual pages"
+ @echo " changes to make an overview of all changed/added/deprecated items"
+ @echo " linkcheck to check all external links for integrity"
+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+ -rm -rf $(BUILDDIR)/*
+
+html:
+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+ @echo
+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+ @echo
+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+ @echo
+ @echo "Build finished; now you can process the pickle files."
+
+json:
+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+ @echo
+ @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+ @echo
+ @echo "Build finished; now you can run HTML Help Workshop with the" \
+ ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+ @echo
+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/$(PROJECT).qhcp"
+ @echo "To view the help file:"
+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/$(PROJECT).qhc"
+
+devhelp:
+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+ @echo
+ @echo "Build finished."
+ @echo "To view the help file:"
+ @echo "# mkdir -p $$HOME/.local/share/devhelp/$(PROJECT)"
+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/$(PROJECT)"
+ @echo "# devhelp"
+
+epub:
+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+ @echo
+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo
+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
+ "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+ @echo "Running LaTeX files through pdflatex..."
+ make -C $(BUILDDIR)/latex all-pdf
+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+ @echo
+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+ @echo
+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+changes:
+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+ @echo
+ @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+ @echo
+ @echo "Link check complete; look for any errors in the above output " \
+ "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+ @echo "Testing of doctests in the sources finished, look at the " \
+ "results in $(BUILDDIR)/doctest/output.txt."
View
45 docs/changelog.rst
@@ -0,0 +1,45 @@
+.. _changelog:
+
+ChangeLog
+=========
+
+0.2
+---
+
+- added ability to publish for periods of time
+- added model to store permanent clearings (see migration below)
+- added ability to control how announcements are cleared (no
+ clearing, session based, or permanent) (see migration below)
+- changed view `announcement_hide` to `dismiss`
+- changed url name of `announcement_hide` to `announcement_dismiss`
+- changed template tag from fetch_announcements to announcements
+- removed send now functionality
+- removed notifications
+- removed context processor
+- removed list view
+- removed AnnouncementsManager
+- removed current_announcements_for_request
+
+
+Migrations
+^^^^^^^^^^
+
+ ALTER TABLE "announcements_announcement" ADD COLUMN "dismissal_type" int DEFAULT 2 NOT NULL;
+ ALTER TABLE "announcements_announcement" ADD COLUMN "publish_start" timestamp with time zone NOT NULL;
+ ALTER TABLE "announcements_announcement" ADD COLUMN "publish_end" timestamp with time zone;
+ ### New Model: announcements.Dismissal
+ CREATE TABLE "announcements_dismissal" (
+ "id" serial NOT NULL PRIMARY KEY,
+ "user_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED,
+ "announcement_id" integer NOT NULL REFERENCES "announcements_announcement" ("id") DEFERRABLE INITIALLY DEFERRED,
+ "dismissed_at" timestamp with time zone NOT NULL
+ )
+ ;
+ CREATE INDEX "announcements_dismissal_user_id" ON "announcements_dismissal" ("user_id");
+ CREATE INDEX "announcements_dismissal_announcement_id" ON "announcements_dismissal" ("announcement_id");
+
+
+0.1
+---
+
+- initial release
View
28 docs/conf.py
@@ -0,0 +1,28 @@
+import sys, os
+
+extensions = []
+templates_path = []
+source_suffix = '.rst'
+master_doc = 'index'
+project = u'django-announcements'
+package = 'announcements'
+copyright_holder = 'Eldarion'
+copyright = u'2011, %s' % copyright_holder
+exclude_patterns = ['_build']
+pygments_style = 'sphinx'
+html_theme = 'default'
+htmlhelp_basename = '%sdoc' % project
+latex_documents = [
+ ('index', '%s.tex' % project, u'%s Documentation' % project,
+ copyright_holder, 'manual'),
+]
+man_pages = [
+ ('index', project, u'%s Documentation' % project,
+ [copyright_holder], 1)
+]
+
+sys.path.insert(0, os.pardir)
+m = __import__(package)
+
+version = m.__version__
+release = version
View
29 docs/index.rst
@@ -0,0 +1,29 @@
+====================
+django-announcements
+====================
+
+Some sites need the ability to broadcast an announcement to all of their
+users. django-announcements was created precisely for this reason. How you
+present the announcement is up to you as the site-developer. There are two
+different types of filtering of announcements:
+
+ * site-wide (this can be presented to anonymous users)
+ * members only (announcements for only logged in users)
+
+
+Development
+-----------
+
+The source repository can be found at https://github.com/pinax/django-announcements
+
+
+Contents
+========
+
+.. toctree::
+ :maxdepth: 1
+
+ changelog
+ installation
+ templatetags
+ usage
View
23 docs/index.txt
@@ -1,23 +0,0 @@
-
-====================
-django-announcements
-====================
-
-Some sites need the ability to broadcast an announcement to all of their
-users. django-announcements was created precisely for this reason. How you
-present the announcement is up to you as the site-developer. When working with
-announcements that are presented on the website one feature is that they are
-only viewed once. A session variable will hold which announcements an user has
-viewed and exclude that from their display. announcements supports two
-different types of filtering of announcements:
-
- * site-wide (this can be presented to anonymous users)
- * non site-wide (these can be used a strictly a mailing if so desired)
- * members only (announcements are filtered based on the value of
- ``request.user``)
-
-Contents:
-
-.. toctree::
-
- usage
View
21 docs/installation.rst
@@ -0,0 +1,21 @@
+.. _installation:
+
+Installation
+============
+
+* To install django-announcements::
+
+ pip install django-announcements
+
+* Add ``'announcements'`` to your ``INSTALLED_APPS`` setting::
+
+ INSTALLED_APPS = (
+ # other apps
+ "announcements",
+ )
+
+* Finally::
+
+ ...
+ url(r"^announcements/", include("announcements.urls")),
+ ...
View
9 docs/templatetags.rst
@@ -0,0 +1,9 @@
+.. _templatetags:
+
+Template Tags
+=============
+
+announcements
+-------------
+
+ {% announcements as announcements_list %}
View
39 docs/usage.rst
@@ -0,0 +1,39 @@
+.. _usage:
+
+Usage
+=====
+
+Displaying announcements is done via a template tag that fetches the
+announcements::
+
+ {% load announcements_tags %}
+
+ <h3>Announcements</h3>
+
+ {% announcements as announcements_list %}
+
+ {% if announcements_list %}
+ <div class="announcements">
+ {% for announcement in announcements_list %}
+ <div class="announcement">
+ <strong>{{ announcement.title }}</strong><br />
+ {{ announcement.content }}
+ {% if announcement.dismiss_url %}
+ <a href="{{ announcement.dismiss_url }}" class="dismiss">
+ Clear
+ </a>
+ {% endif %}
+ </div>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+If you expect your announcement to be more detail oriented rather than
+just a few sentences then it might be better a link in the mark up to
+the supplied detail view::
+
+ <a href="{{ announcement.get_absolute_url }}">Read more...</a>
+
+
+The `announcement.clear_url` is intended to be called via an AJAX POST
+and will dismiss the announcement based on it's dismissal properties.
View
124 docs/usage.txt
@@ -1,124 +0,0 @@
-
-=====
-Usage
-=====
-
-Basic usage
-===========
-
-Integrating announcements is very simple. announcements provides to you a
-context processor to get template global access::
-
- TEMPLATE_CONTEXT_PROCESSORS = (
- # ...
- "announcements.context_processors.site_wide_announcements",
- # ...
- )
-
-Once that is hooked up you now have access ``{{ site_wide_announcements }}``
-which is a simple queryset that has filtered the announcements to give you
-just the right ones. If the user viewing the page is authenticated it will
-additionally pull out announcements that have been marked ``for_members``.
-
-Here is a quick snippet of how this can be used in a template. Typically in
-a base template like ``base.html`` or some sort::
-
- {% if site_wide_announcements %}
- <div id="site_wide_announcements">
- <ul>
- {% for announcement in site_wide_announcements %}
- <li>
- <a href="{{ announcement.get_absolute_url }}">{{ announcement }}</a> -
- <a href="{% url announcement_hide announcement.pk %}?next={{ request.path }}">Hide announcement</a>
- </li>
- {% endfor %}
- </ul>
- </div>
- {% endif %}
-
-The above template sample uses the views discussed a little bit later on.
-
-To give a bit of internals, the ``site_wide_announcements`` context processor
-is simply a wrapper around ``current_announcements_for_request`` which is
-located in ``announcements.models``.
-
-Announcement model
-------------------
-
-To store announcements in the database, announcements comes with a model that
-deals with this. It contains these fields:
-
- * ``title`` - The title of the announcement. This is limited to 50 characters.
- The title is completely optional since some types of announcements don't
- really need one.
- * ``content`` - The main content of the announcement.
- * ``creator`` - The user who created the announcement.
- * ``creation_date`` - A ``DateTimeField`` indicating when the announcement
- was created.
- * ``site_wide`` - A boolean value indicating whether the announcement should
- be site-wide and used in the context processor.
- * ``members_only`` - This will tag an announcement as for member eyes only.
-
-Additional uses
-===============
-
-There are a couple of ways that announcements can be used outside of its basic
-usage described above.
-
-E-mailing users
----------------
-
-When you are creating a new announcement via the admin interface you are given
-the option to send now. What this means is that announcements has optional
-support of django-notification. If it is available it can send a notification
-of the announcement. This then in turn can be e-mail to the user.
-
-.. note::
-
- Due to the possibility of large user bases, even 20+, can cause the
- sending of a notification to take a bit of time. This could in turn cause
- the request to time out. To avoid that announcements uses the queuing
- feature of notifications. To send out the notifications you will need to
- use the ``emit_notices`` management command notifications provides.
-
-URLconf, views and templates
-----------------------------
-
-announcements comes with three pre-defined URLs. They enable you the ability
-to list, view and hide announcements. You can hook up these views very
-simply in your ``urls.py``::
-
- # example urls.py.
- from django.conf.urls.defaults import *
-
- urlpatterns = patterns("",
- # ...
- url(r"^announcements/", include("announcements.urls")),
- # ...
- )
-
-``announcement_home``
-~~~~~~~~~~~~~~~~~~~~~
-
-View: ``announcements.views.announcement_list``
-
-It uses ``current_announcements_for_request`` to get a queryset of
-announcements appropriate to the ``HttpRequest``.
-
-``announcement_detail``
-~~~~~~~~~~~~~~~~~~~~~~~
-
-View: ``django.views.generic.list_detail.object_detail``
-
-Displays a single announcement. Reference the Django object_detail_
-documentation for more information.
-
-.. _object_detail: http://docs.djangoproject.com/en/dev/ref/generic-views/#django-views-generic-list-detail-object-detail
-
-``announcement_hide``
-~~~~~~~~~~~~~~~~~~~~~
-
-View: ``announcements.views.announcement_hide``
-
-This view will mark a given announcement as hidden and redirect the user
-to the provide ``next`` ``GET`` argument.
View
140 setup.py
@@ -1,19 +1,129 @@
-from distutils.core import setup
+import codecs
+import os
+import sys
+
+from distutils.util import convert_path
+from fnmatch import fnmatchcase
+from setuptools import setup, find_packages
+
+
+def read(fname):
+ return codecs.open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+
+# Provided as an attribute, so you can append to these instead
+# of replicating them:
+standard_exclude = ["*.py", "*.pyc", "*$py.class", "*~", ".*", "*.bak"]
+standard_exclude_directories = [
+ ".*", "CVS", "_darcs", "./build", "./dist", "EGG-INFO", "*.egg-info"
+]
+
+
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+# Note: you may want to copy this into your setup.py file verbatim, as
+# you can't import this from another package, when you don't know if
+# that package is installed yet.
+def find_package_data(
+ where=".",
+ package="",
+ exclude=standard_exclude,
+ exclude_directories=standard_exclude_directories,
+ only_in_packages=True,
+ show_ignored=False):
+ """
+ Return a dictionary suitable for use in ``package_data``
+ in a distutils ``setup.py`` file.
+
+ The dictionary looks like::
+
+ {"package": [files]}
+
+ Where ``files`` is a list of all the files in that package that
+ don"t match anything in ``exclude``.
+
+ If ``only_in_packages`` is true, then top-level directories that
+ are not packages won"t be included (but directories under packages
+ will).
+
+ Directories matching any pattern in ``exclude_directories`` will
+ be ignored; by default directories with leading ``.``, ``CVS``,
+ and ``_darcs`` will be ignored.
+
+ If ``show_ignored`` is true, then all the files that aren"t
+ included in package data are shown on stderr (for debugging
+ purposes).
+
+ Note patterns use wildcards, or can be exact paths (including
+ leading ``./``), and all searching is case-insensitive.
+ """
+ out = {}
+ stack = [(convert_path(where), "", package, only_in_packages)]
+ while stack:
+ where, prefix, package, only_in_packages = stack.pop(0)
+ for name in os.listdir(where):
+ fn = os.path.join(where, name)
+ if os.path.isdir(fn):
+ bad_name = False
+ for pattern in exclude_directories:
+ if (fnmatchcase(name, pattern)
+ or fn.lower() == pattern.lower()):
+ bad_name = True
+ if show_ignored:
+ print >> sys.stderr, (
+ "Directory %s ignored by pattern %s"
+ % (fn, pattern))
+ break
+ if bad_name:
+ continue
+ if (os.path.isfile(os.path.join(fn, "__init__.py"))
+ and not prefix):
+ if not package:
+ new_package = name
+ else:
+ new_package = package + "." + name
+ stack.append((fn, "", new_package, False))
+ else:
+ stack.append((fn, prefix + name + "/", package, only_in_packages))
+ elif package or not only_in_packages:
+ # is a file
+ bad_name = False
+ for pattern in exclude:
+ if (fnmatchcase(name, pattern)
+ or fn.lower() == pattern.lower()):
+ bad_name = True
+ if show_ignored:
+ print >> sys.stderr, (
+ "File %s ignored by pattern %s"
+ % (fn, pattern))
+ break
+ if bad_name:
+ continue
+ out.setdefault(package, []).append(prefix+name)
+ return out
+
+
+PACKAGE = "announcements"
+NAME = "django-announcements"
+DESCRIPTION = "Announcements for your Django powered website."
+AUTHOR = "Brian Rosner"
+AUTHOR_EMAIL = "brosner@gmail.com"
+URL = "https://github.com/pinax/django-announcements"
+VERSION = __import__(PACKAGE).__version__
setup(
- name = "django-announcements",
- version = __import__("announcements").__version__,
- author = "Brian Rosner",
- author_email = "brosner@gmail.com",
- description = "Announcements for your Django powered website.",
- long_description = open("README").read(),
- license = "MIT",
- url = "http://code.google.com/p/django-announcements",
- packages = [
- "announcements",
- ],
- classifiers = [
+ name=NAME,
+ version=VERSION,
+ description=DESCRIPTION,
+ long_description=read("README"),
+ author=AUTHOR,
+ author_email=AUTHOR_EMAIL,
+ license="MIT",
+ url=URL,
+ packages=find_packages(exclude=["tests.*", "tests"]),
+ package_data=find_package_data(PACKAGE, only_in_packages=False),
+ classifiers=[
"Development Status :: 3 - Alpha",
"Environment :: Web Environment",
"Intended Audience :: Developers",
@@ -21,5 +131,7 @@
"Operating System :: OS Independent",
"Programming Language :: Python",
"Framework :: Django",
- ]
+ ],
+ zip_safe=False,
)
+
Something went wrong with that request. Please try again.