Fix flake8 warnings
This commit is contained in:
		
							parent
							
								
									96596dc5dd
								
							
						
					
					
						commit
						36ab6d599f
					
				
					 66 changed files with 614 additions and 585 deletions
				
			
		|  | @ -13,8 +13,8 @@ def default_can_edit(request, *args, **kwargs): | ||||||
| 
 | 
 | ||||||
| def load_can_edit(): | def load_can_edit(): | ||||||
|     import_path = getattr(settings, "BOXES_CAN_EDIT_CALLABLE", None) |     import_path = getattr(settings, "BOXES_CAN_EDIT_CALLABLE", None) | ||||||
|      | 
 | ||||||
|     if import_path is None: |     if import_path is None: | ||||||
|         return default_can_edit |         return default_can_edit | ||||||
|      | 
 | ||||||
|     return load_path_attr(import_path) |     return load_path_attr(import_path) | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ from symposion.boxes.models import Box | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BoxForm(forms.ModelForm): | class BoxForm(forms.ModelForm): | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = Box |         model = Box | ||||||
|         fields = ["content"] |         fields = ["content"] | ||||||
|  |  | ||||||
|  | @ -1,5 +1,3 @@ | ||||||
| import datetime |  | ||||||
| 
 |  | ||||||
| from django.db import models | from django.db import models | ||||||
| 
 | 
 | ||||||
| from django.contrib.auth.models import User | from django.contrib.auth.models import User | ||||||
|  | @ -10,16 +8,16 @@ from markitup.fields import MarkupField | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Box(models.Model): | class Box(models.Model): | ||||||
|      | 
 | ||||||
|     label = models.CharField(max_length=100, db_index=True) |     label = models.CharField(max_length=100, db_index=True) | ||||||
|     content = MarkupField(blank=True) |     content = MarkupField(blank=True) | ||||||
|      | 
 | ||||||
|     created_by = models.ForeignKey(User, related_name="boxes") |     created_by = models.ForeignKey(User, related_name="boxes") | ||||||
|     last_updated_by = models.ForeignKey(User, related_name="updated_boxes") |     last_updated_by = models.ForeignKey(User, related_name="updated_boxes") | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.label |         return self.label | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         verbose_name_plural = "boxes" |         verbose_name_plural = "boxes" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,10 +1,5 @@ | ||||||
| from django import template | from django import template | ||||||
| from django.core.exceptions import ImproperlyConfigured |  | ||||||
| from django.core.urlresolvers import reverse | from django.core.urlresolvers import reverse | ||||||
| from django.utils.safestring import mark_safe |  | ||||||
| from django.utils.encoding import smart_str |  | ||||||
| from django.utils.translation import ugettext_lazy as _ |  | ||||||
| from django.template.defaulttags import kwarg_re |  | ||||||
| 
 | 
 | ||||||
| from symposion.boxes.models import Box | from symposion.boxes.models import Box | ||||||
| from symposion.boxes.forms import BoxForm | from symposion.boxes.forms import BoxForm | ||||||
|  | @ -16,22 +11,22 @@ register = template.Library() | ||||||
| 
 | 
 | ||||||
| @register.inclusion_tag("boxes/box.html", takes_context=True) | @register.inclusion_tag("boxes/box.html", takes_context=True) | ||||||
| def box(context, label, show_edit=True, *args, **kwargs): | def box(context, label, show_edit=True, *args, **kwargs): | ||||||
|      | 
 | ||||||
|     request = context["request"] |     request = context["request"] | ||||||
|     can_edit = load_can_edit()(request, *args, **kwargs) |     can_edit = load_can_edit()(request, *args, **kwargs) | ||||||
|      | 
 | ||||||
|     try: |     try: | ||||||
|         box = Box.objects.get(label=label) |         box = Box.objects.get(label=label) | ||||||
|     except Box.DoesNotExist: |     except Box.DoesNotExist: | ||||||
|         box = None |         box = None | ||||||
|      | 
 | ||||||
|     if can_edit and show_edit: |     if can_edit and show_edit: | ||||||
|         form = BoxForm(instance=box, prefix=label) |         form = BoxForm(instance=box, prefix=label) | ||||||
|         form_action = reverse("box_edit", args=[label]) |         form_action = reverse("box_edit", args=[label]) | ||||||
|     else: |     else: | ||||||
|         form = None |         form = None | ||||||
|         form_action = None |         form_action = None | ||||||
|      | 
 | ||||||
|     return { |     return { | ||||||
|         "request": request, |         "request": request, | ||||||
|         "label": label, |         "label": label, | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
|  | # flake8: noqa | ||||||
| from django.conf.urls.defaults import url, patterns | from django.conf.urls.defaults import url, patterns | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| urlpatterns = patterns("symposion.boxes.views", | urlpatterns = patterns("symposion.boxes.views", | ||||||
|     url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"), |     url(r"^([-\w]+)/edit/$", "box_edit", name="box_edit"), | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -7,7 +7,8 @@ from symposion.boxes.forms import BoxForm | ||||||
| from symposion.boxes.models import Box | from symposion.boxes.models import Box | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # @@@ problem with this is that the box_edit.html and box_create.html won't have domain objects in context | # @@@ problem with this is that the box_edit.html and box_create.html won't have domain objects in | ||||||
|  | # context | ||||||
| def get_auth_vars(request): | def get_auth_vars(request): | ||||||
|     auth_vars = {} |     auth_vars = {} | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|  | @ -20,17 +21,17 @@ def get_auth_vars(request): | ||||||
| 
 | 
 | ||||||
| @require_POST | @require_POST | ||||||
| def box_edit(request, label): | def box_edit(request, label): | ||||||
|      | 
 | ||||||
|     if not load_can_edit()(request, **get_auth_vars(request)): |     if not load_can_edit()(request, **get_auth_vars(request)): | ||||||
|         return HttpResponseForbidden() |         return HttpResponseForbidden() | ||||||
|      | 
 | ||||||
|     next = request.GET.get("next") |     next = request.GET.get("next") | ||||||
|      | 
 | ||||||
|     try: |     try: | ||||||
|         box = Box.objects.get(label=label) |         box = Box.objects.get(label=label) | ||||||
|     except Box.DoesNotExist: |     except Box.DoesNotExist: | ||||||
|         box = None |         box = None | ||||||
|      | 
 | ||||||
|     form = BoxForm(request.POST, instance=box, prefix=label) |     form = BoxForm(request.POST, instance=box, prefix=label) | ||||||
| 
 | 
 | ||||||
|     if form.is_valid(): |     if form.is_valid(): | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import reversion | ||||||
| 
 | 
 | ||||||
| from .models import Page | from .models import Page | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class PageAdmin(reversion.VersionAdmin): | class PageAdmin(reversion.VersionAdmin): | ||||||
| 
 | 
 | ||||||
|     pass |     pass | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ from .models import Page | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class PageForm(forms.ModelForm): | class PageForm(forms.ModelForm): | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = Page |         model = Page | ||||||
|         fields = ["title", "body", "path"] |         fields = ["title", "body", "path"] | ||||||
|  | @ -17,5 +17,5 @@ class PageForm(forms.ModelForm): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FileUploadForm(forms.Form): | class FileUploadForm(forms.Form): | ||||||
|      | 
 | ||||||
|     file = forms.FileField() |     file = forms.FileField() | ||||||
|  |  | ||||||
|  | @ -2,8 +2,9 @@ from datetime import datetime | ||||||
| 
 | 
 | ||||||
| from django.db import models | from django.db import models | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class PublishedPageManager(models.Manager): | class PublishedPageManager(models.Manager): | ||||||
|      | 
 | ||||||
|     def get_query_set(self): |     def get_query_set(self): | ||||||
|         qs = super(PublishedPageManager, self).get_query_set() |         qs = super(PublishedPageManager, self).get_query_set() | ||||||
|         return qs.filter(publish_date__lte=datetime.now()) |         return qs.filter(publish_date__lte=datetime.now()) | ||||||
|  |  | ||||||
|  | @ -18,12 +18,12 @@ from .managers import PublishedPageManager | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Page(models.Model): | class Page(models.Model): | ||||||
|      | 
 | ||||||
|     STATUS_CHOICES = ( |     STATUS_CHOICES = ( | ||||||
|         (1, _("Draft")), |         (1, _("Draft")), | ||||||
|         (2, _("Public")), |         (2, _("Public")), | ||||||
|     ) |     ) | ||||||
|      | 
 | ||||||
|     title = models.CharField(max_length=100) |     title = models.CharField(max_length=100) | ||||||
|     path = models.CharField(max_length=100, unique=True) |     path = models.CharField(max_length=100, unique=True) | ||||||
|     body = MarkupField() |     body = MarkupField() | ||||||
|  | @ -32,28 +32,29 @@ class Page(models.Model): | ||||||
|     created = models.DateTimeField(editable=False, default=datetime.datetime.now) |     created = models.DateTimeField(editable=False, default=datetime.datetime.now) | ||||||
|     updated = models.DateTimeField(editable=False, default=datetime.datetime.now) |     updated = models.DateTimeField(editable=False, default=datetime.datetime.now) | ||||||
|     tags = TaggableManager(blank=True) |     tags = TaggableManager(blank=True) | ||||||
|      | 
 | ||||||
|     published = PublishedPageManager() |     published = PublishedPageManager() | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.title |         return self.title | ||||||
|      | 
 | ||||||
|     @models.permalink |     @models.permalink | ||||||
|     def get_absolute_url(self): |     def get_absolute_url(self): | ||||||
|         return ("cms_page", [self.path]) |         return ("cms_page", [self.path]) | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def is_community(self): |     def is_community(self): | ||||||
|         return self.path.lower().startswith("community/") |         return self.path.lower().startswith("community/") | ||||||
|      | 
 | ||||||
|     def save(self, *args, **kwargs): |     def save(self, *args, **kwargs): | ||||||
|         self.updated = datetime.datetime.now() |         self.updated = datetime.datetime.now() | ||||||
|         super(Page, self).save(*args, **kwargs) |         super(Page, self).save(*args, **kwargs) | ||||||
|      | 
 | ||||||
|     def clean_fields(self, exclude=None): |     def clean_fields(self, exclude=None): | ||||||
|         super(Page, self).clean_fields(exclude) |         super(Page, self).clean_fields(exclude) | ||||||
|         if not re.match(settings.SYMPOSION_PAGE_REGEX, self.path): |         if not re.match(settings.SYMPOSION_PAGE_REGEX, self.path): | ||||||
|             raise ValidationError({"path": [_("Path can only contain letters, numbers and hyphens and end with /")]}) |             raise ValidationError( | ||||||
|  |                 {"path": [_("Path can only contain letters, numbers and hyphens and end with /")]}) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| reversion.register(Page) | reversion.register(Page) | ||||||
|  | @ -64,9 +65,9 @@ def generate_filename(instance, filename): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class File(models.Model): | class File(models.Model): | ||||||
|      | 
 | ||||||
|     file = models.FileField(upload_to=generate_filename) |     file = models.FileField(upload_to=generate_filename) | ||||||
|     created = models.DateTimeField(default=datetime.datetime.now) |     created = models.DateTimeField(default=datetime.datetime.now) | ||||||
|      | 
 | ||||||
|     def download_url(self): |     def download_url(self): | ||||||
|         return reverse("file_download", args=[self.pk, os.path.basename(self.file.name).lower()]) |         return reverse("file_download", args=[self.pk, os.path.basename(self.file.name).lower()]) | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # flake8: noqa | ||||||
| from django.conf.urls.defaults import url, patterns | from django.conf.urls.defaults import url, patterns | ||||||
| 
 | 
 | ||||||
| PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/" | PAGE_RE = r"(([\w-]{1,})(/[\w-]{1,})*)/" | ||||||
|  |  | ||||||
|  | @ -23,20 +23,20 @@ def can_upload(user): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def page(request, path): | def page(request, path): | ||||||
|      | 
 | ||||||
|     try: |     try: | ||||||
|         page = Page.published.get(path=path) |         page = Page.published.get(path=path) | ||||||
|     except Page.DoesNotExist: |     except Page.DoesNotExist: | ||||||
|         page = None |         page = None | ||||||
|      | 
 | ||||||
|     editable = can_edit(page, request.user) |     editable = can_edit(page, request.user) | ||||||
|      | 
 | ||||||
|     if page is None: |     if page is None: | ||||||
|         if editable: |         if editable: | ||||||
|             return redirect("cms_page_edit", path=path) |             return redirect("cms_page_edit", path=path) | ||||||
|         else: |         else: | ||||||
|             raise Http404 |             raise Http404 | ||||||
|      | 
 | ||||||
|     return render(request, "cms/page_detail.html", { |     return render(request, "cms/page_detail.html", { | ||||||
|         "page": page, |         "page": page, | ||||||
|         "editable": editable, |         "editable": editable, | ||||||
|  | @ -45,15 +45,15 @@ def page(request, path): | ||||||
| 
 | 
 | ||||||
| @login_required | @login_required | ||||||
| def page_edit(request, path): | def page_edit(request, path): | ||||||
|      | 
 | ||||||
|     try: |     try: | ||||||
|         page = Page.published.get(path=path) |         page = Page.published.get(path=path) | ||||||
|     except Page.DoesNotExist: |     except Page.DoesNotExist: | ||||||
|         page = None |         page = None | ||||||
|      | 
 | ||||||
|     if not can_edit(page, request.user): |     if not can_edit(page, request.user): | ||||||
|         raise Http404 |         raise Http404 | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         form = PageForm(request.POST, instance=page) |         form = PageForm(request.POST, instance=page) | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|  | @ -65,7 +65,7 @@ def page_edit(request, path): | ||||||
|             print form.errors |             print form.errors | ||||||
|     else: |     else: | ||||||
|         form = PageForm(instance=page, initial={"path": path}) |         form = PageForm(instance=page, initial={"path": path}) | ||||||
|      | 
 | ||||||
|     return render(request, "cms/page_edit.html", { |     return render(request, "cms/page_edit.html", { | ||||||
|         "path": path, |         "path": path, | ||||||
|         "form": form |         "form": form | ||||||
|  | @ -75,7 +75,7 @@ def page_edit(request, path): | ||||||
| def file_index(request): | def file_index(request): | ||||||
|     if not can_upload(request.user): |     if not can_upload(request.user): | ||||||
|         raise Http404 |         raise Http404 | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "files": File.objects.all(), |         "files": File.objects.all(), | ||||||
|     } |     } | ||||||
|  | @ -85,7 +85,7 @@ def file_index(request): | ||||||
| def file_create(request): | def file_create(request): | ||||||
|     if not can_upload(request.user): |     if not can_upload(request.user): | ||||||
|         raise Http404 |         raise Http404 | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         form = FileUploadForm(request.POST, request.FILES) |         form = FileUploadForm(request.POST, request.FILES) | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|  | @ -97,7 +97,7 @@ def file_create(request): | ||||||
|             return redirect("file_index") |             return redirect("file_index") | ||||||
|     else: |     else: | ||||||
|         form = FileUploadForm() |         form = FileUploadForm() | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "form": form, |         "form": form, | ||||||
|     } |     } | ||||||
|  | @ -106,7 +106,7 @@ def file_create(request): | ||||||
| 
 | 
 | ||||||
| def file_download(request, pk, *args): | def file_download(request, pk, *args): | ||||||
|     file = get_object_or_404(File, pk=pk) |     file = get_object_or_404(File, pk=pk) | ||||||
|      | 
 | ||||||
|     if getattr(settings, "USE_X_ACCEL_REDIRECT", False): |     if getattr(settings, "USE_X_ACCEL_REDIRECT", False): | ||||||
|         response = HttpResponse() |         response = HttpResponse() | ||||||
|         response["X-Accel-Redirect"] = file.file.url |         response["X-Accel-Redirect"] = file.file.url | ||||||
|  | @ -115,14 +115,14 @@ def file_download(request, pk, *args): | ||||||
|         del response["content-type"] |         del response["content-type"] | ||||||
|     else: |     else: | ||||||
|         response = static.serve(request, file.file.name, document_root=settings.MEDIA_ROOT) |         response = static.serve(request, file.file.name, document_root=settings.MEDIA_ROOT) | ||||||
|      | 
 | ||||||
|     return response |     return response | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def file_delete(request, pk): | def file_delete(request, pk): | ||||||
|     if not can_upload(request.user): |     if not can_upload(request.user): | ||||||
|         raise Http404 |         raise Http404 | ||||||
|      | 
 | ||||||
|     file = get_object_or_404(File, pk=pk) |     file = get_object_or_404(File, pk=pk) | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         file.delete() |         file.delete() | ||||||
|  |  | ||||||
|  | @ -1,8 +1,6 @@ | ||||||
| from django.conf import settings |  | ||||||
| 
 |  | ||||||
| from appconf import AppConf | from appconf import AppConf | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SymposionAppConf(AppConf): | class SymposionAppConf(AppConf): | ||||||
|      | 
 | ||||||
|     VOTE_THRESHOLD = 3 |     VOTE_THRESHOLD = 3 | ||||||
|  |  | ||||||
|  | @ -6,6 +6,6 @@ from symposion.conference.models import Conference, Section | ||||||
| admin.site.register(Conference, list_display=("title", "start_date", "end_date")) | admin.site.register(Conference, list_display=("title", "start_date", "end_date")) | ||||||
| admin.site.register( | admin.site.register( | ||||||
|     Section, |     Section, | ||||||
|     prepopulated_fields = {"slug": ("name",)}, |     prepopulated_fields={"slug": ("name",)}, | ||||||
|     list_display = ("name", "conference", "start_date", "end_date") |     list_display=("name", "conference", "start_date", "end_date") | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -11,24 +11,24 @@ class Conference(models.Model): | ||||||
|     """ |     """ | ||||||
|     the full conference for a specific year, e.g. US PyCon 2012. |     the full conference for a specific year, e.g. US PyCon 2012. | ||||||
|     """ |     """ | ||||||
|      | 
 | ||||||
|     title = models.CharField(_("title"), max_length=100) |     title = models.CharField(_("title"), max_length=100) | ||||||
|      | 
 | ||||||
|     # when the conference runs |     # when the conference runs | ||||||
|     start_date = models.DateField(_("start date"), null=True, blank=True) |     start_date = models.DateField(_("start date"), null=True, blank=True) | ||||||
|     end_date = models.DateField(_("end date"), null=True, blank=True) |     end_date = models.DateField(_("end date"), null=True, blank=True) | ||||||
|      | 
 | ||||||
|     # timezone the conference is in |     # timezone the conference is in | ||||||
|     timezone = TimeZoneField(_("timezone"), blank=True) |     timezone = TimeZoneField(_("timezone"), blank=True) | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.title |         return self.title | ||||||
|      | 
 | ||||||
|     def save(self, *args, **kwargs): |     def save(self, *args, **kwargs): | ||||||
|         super(Conference, self).save(*args, **kwargs) |         super(Conference, self).save(*args, **kwargs) | ||||||
|         if self.id in CONFERENCE_CACHE: |         if self.id in CONFERENCE_CACHE: | ||||||
|             del CONFERENCE_CACHE[self.id] |             del CONFERENCE_CACHE[self.id] | ||||||
|      | 
 | ||||||
|     def delete(self): |     def delete(self): | ||||||
|         pk = self.pk |         pk = self.pk | ||||||
|         super(Conference, self).delete() |         super(Conference, self).delete() | ||||||
|  | @ -36,7 +36,7 @@ class Conference(models.Model): | ||||||
|             del CONFERENCE_CACHE[pk] |             del CONFERENCE_CACHE[pk] | ||||||
|         except KeyError: |         except KeyError: | ||||||
|             pass |             pass | ||||||
|      | 
 | ||||||
|     class Meta(object): |     class Meta(object): | ||||||
|         verbose_name = _("conference") |         verbose_name = _("conference") | ||||||
|         verbose_name_plural = _("conferences") |         verbose_name_plural = _("conferences") | ||||||
|  | @ -48,19 +48,19 @@ class Section(models.Model): | ||||||
|     "Talks", "Expo", "Sprints", that may have its own review and |     "Talks", "Expo", "Sprints", that may have its own review and | ||||||
|     scheduling process. |     scheduling process. | ||||||
|     """ |     """ | ||||||
|      | 
 | ||||||
|     conference = models.ForeignKey(Conference, verbose_name=_("conference")) |     conference = models.ForeignKey(Conference, verbose_name=_("conference")) | ||||||
|      | 
 | ||||||
|     name = models.CharField(_("name"), max_length=100) |     name = models.CharField(_("name"), max_length=100) | ||||||
|     slug = models.SlugField() |     slug = models.SlugField() | ||||||
| 
 | 
 | ||||||
|     # when the section runs |     # when the section runs | ||||||
|     start_date = models.DateField(_("start date"), null=True, blank=True) |     start_date = models.DateField(_("start date"), null=True, blank=True) | ||||||
|     end_date = models.DateField(_("end date"), null=True, blank=True) |     end_date = models.DateField(_("end date"), null=True, blank=True) | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return "%s %s" % (self.conference, self.name) |         return "%s %s" % (self.conference, self.name) | ||||||
|      | 
 | ||||||
|     class Meta(object): |     class Meta(object): | ||||||
|         verbose_name = _("section") |         verbose_name = _("section") | ||||||
|         verbose_name_plural = _("sections") |         verbose_name_plural = _("sections") | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| from django.conf.urls.defaults import * | # flake8: noqa | ||||||
|  | from django.conf.urls.defaults import patterns, url | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| urlpatterns = patterns("symposion.conference.views", | urlpatterns = patterns("symposion.conference.views", | ||||||
|  |  | ||||||
|  | @ -7,10 +7,10 @@ from django.contrib.auth.models import User | ||||||
| 
 | 
 | ||||||
| @login_required | @login_required | ||||||
| def user_list(request): | def user_list(request): | ||||||
|      | 
 | ||||||
|     if not request.user.is_staff: |     if not request.user.is_staff: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     return render(request, "conference/user_list.html", { |     return render(request, "conference/user_list.html", { | ||||||
|         "users": User.objects.all(), |         "users": User.objects.all(), | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import account.forms | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SignupForm(account.forms.SignupForm): | class SignupForm(account.forms.SignupForm): | ||||||
|      | 
 | ||||||
|     first_name = forms.CharField() |     first_name = forms.CharField() | ||||||
|     last_name = forms.CharField() |     last_name = forms.CharField() | ||||||
|     email_confirm = forms.EmailField(label="Confirm Email") |     email_confirm = forms.EmailField(label="Confirm Email") | ||||||
|  | @ -20,11 +20,12 @@ class SignupForm(account.forms.SignupForm): | ||||||
|             "password", |             "password", | ||||||
|             "password_confirm" |             "password_confirm" | ||||||
|         ] |         ] | ||||||
|      | 
 | ||||||
|     def clean_email_confirm(self): |     def clean_email_confirm(self): | ||||||
|         email = self.cleaned_data.get("email") |         email = self.cleaned_data.get("email") | ||||||
|         email_confirm = self.cleaned_data["email_confirm"] |         email_confirm = self.cleaned_data["email_confirm"] | ||||||
|         if email: |         if email: | ||||||
|             if email != email_confirm: |             if email != email_confirm: | ||||||
|                 raise forms.ValidationError("Email address must match previously typed email address") |                 raise forms.ValidationError( | ||||||
|  |                     "Email address must match previously typed email address") | ||||||
|         return email_confirm |         return email_confirm | ||||||
|  |  | ||||||
|  | @ -1,14 +1,13 @@ | ||||||
| import html5lib |  | ||||||
| from html5lib import html5parser, sanitizer | from html5lib import html5parser, sanitizer | ||||||
| 
 | 
 | ||||||
| import markdown | import markdown | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def parse(text): | def parse(text): | ||||||
|      | 
 | ||||||
|     # First run through the Markdown parser |     # First run through the Markdown parser | ||||||
|     text = markdown.markdown(text, extensions=["extra"], safe_mode=False) |     text = markdown.markdown(text, extensions=["extra"], safe_mode=False) | ||||||
|      | 
 | ||||||
|     # Sanitize using html5lib |     # Sanitize using html5lib | ||||||
|     bits = [] |     bits = [] | ||||||
|     parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer) |     parser = html5parser.HTMLParser(tokenizer=sanitizer.HTMLSanitizer) | ||||||
|  |  | ||||||
|  | @ -3,9 +3,8 @@ import csv | ||||||
| from django.http import HttpResponse | from django.http import HttpResponse | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def export_as_csv_action( | def export_as_csv_action(description="Export selected objects as CSV file", | ||||||
|     description="Export selected objects as CSV file", |                          fields=None, exclude=None, header=True): | ||||||
|     fields=None, exclude=None, header=True): |  | ||||||
|     """ |     """ | ||||||
|     This function returns an export csv action |     This function returns an export csv action | ||||||
|     'fields' and 'exclude' work like in Django ModelForm |     'fields' and 'exclude' work like in Django ModelForm | ||||||
|  | @ -24,12 +23,14 @@ def export_as_csv_action( | ||||||
|             excludeset = set(exclude) |             excludeset = set(exclude) | ||||||
|             field_names = field_names - excludeset |             field_names = field_names - excludeset | ||||||
|         response = HttpResponse(mimetype="text/csv") |         response = HttpResponse(mimetype="text/csv") | ||||||
|         response["Content-Disposition"] = "attachment; filename=%s.csv" % unicode(opts).replace(".", "_") |         response["Content-Disposition"] = \ | ||||||
|  |             "attachment; filename=%s.csv" % unicode(opts).replace(".", "_") | ||||||
|         writer = csv.writer(response) |         writer = csv.writer(response) | ||||||
|         if header: |         if header: | ||||||
|             writer.writerow(list(field_names)) |             writer.writerow(list(field_names)) | ||||||
|         for obj in queryset: |         for obj in queryset: | ||||||
|             writer.writerow([unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names]) |             writer.writerow( | ||||||
|  |                 [unicode(getattr(obj, field)).encode("utf-8", "replace") for field in field_names]) | ||||||
|         return response |         return response | ||||||
|     export_as_csv.short_description = description |     export_as_csv.short_description = description | ||||||
|     return export_as_csv |     return export_as_csv | ||||||
|  |  | ||||||
|  | @ -9,15 +9,15 @@ from symposion.proposals.models import SupportingDocument | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AddSpeakerForm(forms.Form): | class AddSpeakerForm(forms.Form): | ||||||
|      | 
 | ||||||
|     email = forms.EmailField( |     email = forms.EmailField( | ||||||
|         label="Email address of new speaker (use their email address, not yours)" |         label="Email address of new speaker (use their email address, not yours)" | ||||||
|     ) |     ) | ||||||
|      | 
 | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         self.proposal = kwargs.pop("proposal") |         self.proposal = kwargs.pop("proposal") | ||||||
|         super(AddSpeakerForm, self).__init__(*args, **kwargs) |         super(AddSpeakerForm, self).__init__(*args, **kwargs) | ||||||
|      | 
 | ||||||
|     def clean_email(self): |     def clean_email(self): | ||||||
|         value = self.cleaned_data["email"] |         value = self.cleaned_data["email"] | ||||||
|         exists = self.proposal.additional_speakers.filter( |         exists = self.proposal.additional_speakers.filter( | ||||||
|  | @ -32,7 +32,7 @@ class AddSpeakerForm(forms.Form): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SupportingDocumentCreateForm(forms.ModelForm): | class SupportingDocumentCreateForm(forms.ModelForm): | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = SupportingDocument |         model = SupportingDocument | ||||||
|         fields = [ |         fields = [ | ||||||
|  |  | ||||||
|  | @ -3,15 +3,14 @@ from django.db.models.query import QuerySet | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class CachingM2MQuerySet(QuerySet): | class CachingM2MQuerySet(QuerySet): | ||||||
|      | 
 | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         super(CachingM2MQuerySet, self).__init__(*args, **kwargs) |         super(CachingM2MQuerySet, self).__init__(*args, **kwargs) | ||||||
|         self.cached_m2m_field = kwargs["m2m_field"] |         self.cached_m2m_field = kwargs["m2m_field"] | ||||||
|      | 
 | ||||||
|     def iterator(self): |     def iterator(self): | ||||||
|         parent_iter = super(CachingM2MQuerySet, self).iterator() |         parent_iter = super(CachingM2MQuerySet, self).iterator() | ||||||
|         m2m_model = getattr(self.model, self.cached_m2m_field).through | 
 | ||||||
|          |  | ||||||
|         for obj in parent_iter: |         for obj in parent_iter: | ||||||
|             if obj.id in cached_objects: |             if obj.id in cached_objects: | ||||||
|                 setattr(obj, "_cached_m2m_%s" % self.cached_m2m_field) |                 setattr(obj, "_cached_m2m_%s" % self.cached_m2m_field) | ||||||
|  | @ -21,7 +20,3 @@ class CachingM2MQuerySet(QuerySet): | ||||||
| class ProposalManager(models.Manager): | class ProposalManager(models.Manager): | ||||||
|     def cache_m2m(self, m2m_field): |     def cache_m2m(self, m2m_field): | ||||||
|         return CachingM2MQuerySet(self.model, using=self._db, m2m_field=m2m_field) |         return CachingM2MQuerySet(self.model, using=self._db, m2m_field=m2m_field) | ||||||
|         AdditionalSpeaker = queryset.model.additional_speakers.through |  | ||||||
|         additional_speakers = collections.defaultdict(set) |  | ||||||
|         for additional_speaker in AdditionalSpeaker._default_manager.filter(proposal__in=queryset).select_related("speaker__user"): |  | ||||||
|             additional_speakers[additional_speaker.proposal_id].add(additional_speaker.speaker) |  | ||||||
|  | @ -21,20 +21,20 @@ from symposion.conference.models import Section | ||||||
| class ProposalSection(models.Model): | class ProposalSection(models.Model): | ||||||
|     """ |     """ | ||||||
|     configuration of proposal submissions for a specific Section. |     configuration of proposal submissions for a specific Section. | ||||||
|      | 
 | ||||||
|     a section is available for proposals iff: |     a section is available for proposals iff: | ||||||
|       * it is after start (if there is one) and |       * it is after start (if there is one) and | ||||||
|       * it is before end (if there is one) and |       * it is before end (if there is one) and | ||||||
|       * closed is NULL or False |       * closed is NULL or False | ||||||
|     """ |     """ | ||||||
|      | 
 | ||||||
|     section = models.OneToOneField(Section) |     section = models.OneToOneField(Section) | ||||||
|      | 
 | ||||||
|     start = models.DateTimeField(null=True, blank=True) |     start = models.DateTimeField(null=True, blank=True) | ||||||
|     end = models.DateTimeField(null=True, blank=True) |     end = models.DateTimeField(null=True, blank=True) | ||||||
|     closed = models.NullBooleanField() |     closed = models.NullBooleanField() | ||||||
|     published = models.NullBooleanField() |     published = models.NullBooleanField() | ||||||
|      | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def available(cls): |     def available(cls): | ||||||
|         now = datetime.datetime.now() |         now = datetime.datetime.now() | ||||||
|  | @ -43,7 +43,7 @@ class ProposalSection(models.Model): | ||||||
|             Q(end__gt=now) | Q(end=None), |             Q(end__gt=now) | Q(end=None), | ||||||
|             Q(closed=False) | Q(closed=None), |             Q(closed=False) | Q(closed=None), | ||||||
|         ) |         ) | ||||||
|      | 
 | ||||||
|     def is_available(self): |     def is_available(self): | ||||||
|         if self.closed: |         if self.closed: | ||||||
|             return False |             return False | ||||||
|  | @ -53,7 +53,7 @@ class ProposalSection(models.Model): | ||||||
|         if self.end and self.end < now: |         if self.end and self.end < now: | ||||||
|             return False |             return False | ||||||
|         return True |         return True | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.section.name |         return self.section.name | ||||||
| 
 | 
 | ||||||
|  | @ -61,68 +61,77 @@ class ProposalSection(models.Model): | ||||||
| class ProposalKind(models.Model): | class ProposalKind(models.Model): | ||||||
|     """ |     """ | ||||||
|     e.g. talk vs panel vs tutorial vs poster |     e.g. talk vs panel vs tutorial vs poster | ||||||
|      | 
 | ||||||
|     Note that if you have different deadlines, reviewers, etc. you'll want |     Note that if you have different deadlines, reviewers, etc. you'll want | ||||||
|     to distinguish the section as well as the kind. |     to distinguish the section as well as the kind. | ||||||
|     """ |     """ | ||||||
|      | 
 | ||||||
|     section = models.ForeignKey(Section, related_name="proposal_kinds") |     section = models.ForeignKey(Section, related_name="proposal_kinds") | ||||||
|      | 
 | ||||||
|     name = models.CharField(_("Name"), max_length=100) |     name = models.CharField(_("Name"), max_length=100) | ||||||
|     slug = models.SlugField() |     slug = models.SlugField() | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.name |         return self.name | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ProposalBase(models.Model): | class ProposalBase(models.Model): | ||||||
|      | 
 | ||||||
|     objects = InheritanceManager() |     objects = InheritanceManager() | ||||||
|      | 
 | ||||||
|     kind = models.ForeignKey(ProposalKind) |     kind = models.ForeignKey(ProposalKind) | ||||||
|      | 
 | ||||||
|     title = models.CharField(max_length=100) |     title = models.CharField(max_length=100) | ||||||
|     description = models.TextField( |     description = models.TextField( | ||||||
|         _("Brief Description"), |         _("Brief Description"), | ||||||
|         max_length=400,  # @@@ need to enforce 400 in UI |         max_length=400,  # @@@ need to enforce 400 in UI | ||||||
|         help_text="If your proposal is accepted this will be made public and printed in the program. Should be one paragraph, maximum 400 characters." |         help_text=_("If your proposal is accepted this will be made public and printed in the " | ||||||
|  |                     "program. Should be one paragraph, maximum 400 characters.") | ||||||
|     ) |     ) | ||||||
|     abstract = MarkupField( |     abstract = MarkupField( | ||||||
|         _("Detailed Abstract"), |         _("Detailed Abstract"), | ||||||
|         help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit using <a href='http://daringfireball.net/projects/markdown/basics' target='_blank'>Markdown</a>.") |         help_text=_("Detailed outline. Will be made public if your proposal is accepted. Edit " | ||||||
|  |                     "using <a href='http://daringfireball.net/projects/markdown/basics' " | ||||||
|  |                     "target='_blank'>Markdown</a>.") | ||||||
|     ) |     ) | ||||||
|     additional_notes = MarkupField( |     additional_notes = MarkupField( | ||||||
|         blank=True, |         blank=True, | ||||||
|         help_text=_("Anything else you'd like the program committee to know when making their selection: your past experience, etc. This is not made public. Edit using <a href='http://daringfireball.net/projects/markdown/basics' target='_blank'>Markdown</a>.") |         help_text=_("Anything else you'd like the program committee to know when making their " | ||||||
|  |                     "selection: your past experience, etc. This is not made public. Edit using " | ||||||
|  |                     "<a href='http://daringfireball.net/projects/markdown/basics' " | ||||||
|  |                     "target='_blank'>Markdown</a>.") | ||||||
|     ) |     ) | ||||||
|     submitted = models.DateTimeField( |     submitted = models.DateTimeField( | ||||||
|         default=datetime.datetime.now, |         default=datetime.datetime.now, | ||||||
|         editable=False, |         editable=False, | ||||||
|     ) |     ) | ||||||
|     speaker = models.ForeignKey("speakers.Speaker", related_name="proposals") |     speaker = models.ForeignKey("speakers.Speaker", related_name="proposals") | ||||||
|     additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker", blank=True) |     additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker", | ||||||
|  |                                                  blank=True) | ||||||
|     cancelled = models.BooleanField(default=False) |     cancelled = models.BooleanField(default=False) | ||||||
|      | 
 | ||||||
|     def can_edit(self): |     def can_edit(self): | ||||||
|         return True |         return True | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def section(self): |     def section(self): | ||||||
|         return self.kind.section |         return self.kind.section | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def speaker_email(self): |     def speaker_email(self): | ||||||
|         return self.speaker.email |         return self.speaker.email | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def number(self): |     def number(self): | ||||||
|         return str(self.pk).zfill(3) |         return str(self.pk).zfill(3) | ||||||
|      | 
 | ||||||
|     def speakers(self): |     def speakers(self): | ||||||
|         yield self.speaker |         yield self.speaker | ||||||
|         for speaker in self.additional_speakers.exclude(additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED): |         speakers = self.additional_speakers.exclude( | ||||||
|  |             additionalspeaker__status=AdditionalSpeaker.SPEAKING_STATUS_DECLINED) | ||||||
|  |         for speaker in speakers: | ||||||
|             yield speaker |             yield speaker | ||||||
|      | 
 | ||||||
|     def notification_email_context(self): |     def notification_email_context(self): | ||||||
|         return { |         return { | ||||||
|             "title": self.title, |             "title": self.title, | ||||||
|  | @ -135,21 +144,21 @@ reversion.register(ProposalBase) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AdditionalSpeaker(models.Model): | class AdditionalSpeaker(models.Model): | ||||||
|      | 
 | ||||||
|     SPEAKING_STATUS_PENDING = 1 |     SPEAKING_STATUS_PENDING = 1 | ||||||
|     SPEAKING_STATUS_ACCEPTED = 2 |     SPEAKING_STATUS_ACCEPTED = 2 | ||||||
|     SPEAKING_STATUS_DECLINED = 3 |     SPEAKING_STATUS_DECLINED = 3 | ||||||
|      | 
 | ||||||
|     SPEAKING_STATUS = [ |     SPEAKING_STATUS = [ | ||||||
|         (SPEAKING_STATUS_PENDING, _("Pending")), |         (SPEAKING_STATUS_PENDING, _("Pending")), | ||||||
|         (SPEAKING_STATUS_ACCEPTED, _("Accepted")), |         (SPEAKING_STATUS_ACCEPTED, _("Accepted")), | ||||||
|         (SPEAKING_STATUS_DECLINED, _("Declined")), |         (SPEAKING_STATUS_DECLINED, _("Declined")), | ||||||
|     ] |     ] | ||||||
|      | 
 | ||||||
|     speaker = models.ForeignKey("speakers.Speaker") |     speaker = models.ForeignKey("speakers.Speaker") | ||||||
|     proposalbase = models.ForeignKey(ProposalBase) |     proposalbase = models.ForeignKey(ProposalBase) | ||||||
|     status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING) |     status = models.IntegerField(choices=SPEAKING_STATUS, default=SPEAKING_STATUS_PENDING) | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         db_table = "proposals_proposalbase_additional_speakers" |         db_table = "proposals_proposalbase_additional_speakers" | ||||||
|         unique_together = ("speaker", "proposalbase") |         unique_together = ("speaker", "proposalbase") | ||||||
|  | @ -162,14 +171,15 @@ def uuid_filename(instance, filename): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SupportingDocument(models.Model): | class SupportingDocument(models.Model): | ||||||
|      | 
 | ||||||
|     proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents") |     proposal = models.ForeignKey(ProposalBase, related_name="supporting_documents") | ||||||
|      | 
 | ||||||
|     uploaded_by = models.ForeignKey(User) |     uploaded_by = models.ForeignKey(User) | ||||||
|     created_at = models.DateTimeField(default=datetime.datetime.now) |     created_at = models.DateTimeField(default=datetime.datetime.now) | ||||||
|      | 
 | ||||||
|     file = models.FileField(upload_to=uuid_filename) |     file = models.FileField(upload_to=uuid_filename) | ||||||
|     description = models.CharField(max_length=140) |     description = models.CharField(max_length=140) | ||||||
| 
 | 
 | ||||||
|     def download_url(self): |     def download_url(self): | ||||||
|         return reverse("proposal_document_download", args=[self.pk, os.path.basename(self.file.name).lower()]) |         return reverse("proposal_document_download", | ||||||
|  |                        args=[self.pk, os.path.basename(self.file.name).lower()]) | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ register = template.Library() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AssociatedProposalsNode(template.Node): | class AssociatedProposalsNode(template.Node): | ||||||
|      | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def handle_token(cls, parser, token): |     def handle_token(cls, parser, token): | ||||||
|         bits = token.split_contents() |         bits = token.split_contents() | ||||||
|  | @ -15,10 +15,10 @@ class AssociatedProposalsNode(template.Node): | ||||||
|             return cls(bits[2]) |             return cls(bits[2]) | ||||||
|         else: |         else: | ||||||
|             raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) |             raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) | ||||||
|      | 
 | ||||||
|     def __init__(self, context_var): |     def __init__(self, context_var): | ||||||
|         self.context_var = context_var |         self.context_var = context_var | ||||||
|      | 
 | ||||||
|     def render(self, context): |     def render(self, context): | ||||||
|         request = context["request"] |         request = context["request"] | ||||||
|         if request.user.speaker_profile: |         if request.user.speaker_profile: | ||||||
|  | @ -32,7 +32,7 @@ class AssociatedProposalsNode(template.Node): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class PendingProposalsNode(template.Node): | class PendingProposalsNode(template.Node): | ||||||
|      | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def handle_token(cls, parser, token): |     def handle_token(cls, parser, token): | ||||||
|         bits = token.split_contents() |         bits = token.split_contents() | ||||||
|  | @ -40,10 +40,10 @@ class PendingProposalsNode(template.Node): | ||||||
|             return cls(bits[2]) |             return cls(bits[2]) | ||||||
|         else: |         else: | ||||||
|             raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) |             raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) | ||||||
|      | 
 | ||||||
|     def __init__(self, context_var): |     def __init__(self, context_var): | ||||||
|         self.context_var = context_var |         self.context_var = context_var | ||||||
|      | 
 | ||||||
|     def render(self, context): |     def render(self, context): | ||||||
|         request = context["request"] |         request = context["request"] | ||||||
|         if request.user.speaker_profile: |         if request.user.speaker_profile: | ||||||
|  | @ -70,4 +70,3 @@ def associated_proposals(parser, token): | ||||||
|     {% associated_proposals as associated_proposals %} |     {% associated_proposals as associated_proposals %} | ||||||
|     """ |     """ | ||||||
|     return AssociatedProposalsNode.handle_token(parser, token) |     return AssociatedProposalsNode.handle_token(parser, token) | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # flake8: noqa | ||||||
| from django.conf.urls.defaults import * | from django.conf.urls.defaults import * | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -11,7 +12,7 @@ urlpatterns = patterns("symposion.proposals.views", | ||||||
|     url(r"^(\d+)/leave/$", "proposal_leave", name="proposal_leave"), |     url(r"^(\d+)/leave/$", "proposal_leave", name="proposal_leave"), | ||||||
|     url(r"^(\d+)/join/$", "proposal_pending_join", name="proposal_pending_join"), |     url(r"^(\d+)/join/$", "proposal_pending_join", name="proposal_pending_join"), | ||||||
|     url(r"^(\d+)/decline/$", "proposal_pending_decline", name="proposal_pending_decline"), |     url(r"^(\d+)/decline/$", "proposal_pending_decline", name="proposal_pending_decline"), | ||||||
|      | 
 | ||||||
|     url(r"^(\d+)/document/create/$", "document_create", name="proposal_document_create"), |     url(r"^(\d+)/document/create/$", "document_create", name="proposal_document_create"), | ||||||
|     url(r"^document/(\d+)/delete/$", "document_delete", name="proposal_document_delete"), |     url(r"^document/(\d+)/delete/$", "document_delete", name="proposal_document_delete"), | ||||||
|     url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"), |     url(r"^document/(\d+)/([^/]+)$", "document_download", name="proposal_document_download"), | ||||||
|  |  | ||||||
|  | @ -37,21 +37,21 @@ def proposal_submit(request): | ||||||
|             request.user.speaker_profile |             request.user.speaker_profile | ||||||
|         except ObjectDoesNotExist: |         except ObjectDoesNotExist: | ||||||
|             return redirect("dashboard") |             return redirect("dashboard") | ||||||
|      | 
 | ||||||
|     kinds = [] |     kinds = [] | ||||||
|     for proposal_section in ProposalSection.available(): |     for proposal_section in ProposalSection.available(): | ||||||
|         for kind in proposal_section.section.proposal_kinds.all(): |         for kind in proposal_section.section.proposal_kinds.all(): | ||||||
|             kinds.append(kind) |             kinds.append(kind) | ||||||
|      | 
 | ||||||
|     return render(request, "proposals/proposal_submit.html", { |     return render(request, "proposals/proposal_submit.html", { | ||||||
|         "kinds": kinds, |         "kinds": kinds, | ||||||
|     }) |     }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def proposal_submit_kind(request, kind_slug): | def proposal_submit_kind(request, kind_slug): | ||||||
|      | 
 | ||||||
|     kind = get_object_or_404(ProposalKind, slug=kind_slug) |     kind = get_object_or_404(ProposalKind, slug=kind_slug) | ||||||
|      | 
 | ||||||
|     if not request.user.is_authenticated(): |     if not request.user.is_authenticated(): | ||||||
|         return redirect("home")  # @@@ unauth'd speaker info page? |         return redirect("home")  # @@@ unauth'd speaker info page? | ||||||
|     else: |     else: | ||||||
|  | @ -59,12 +59,12 @@ def proposal_submit_kind(request, kind_slug): | ||||||
|             speaker_profile = request.user.speaker_profile |             speaker_profile = request.user.speaker_profile | ||||||
|         except ObjectDoesNotExist: |         except ObjectDoesNotExist: | ||||||
|             return redirect("dashboard") |             return redirect("dashboard") | ||||||
|      | 
 | ||||||
|     if not kind.section.proposalsection.is_available(): |     if not kind.section.proposalsection.is_available(): | ||||||
|         return redirect("proposal_submit") |         return redirect("proposal_submit") | ||||||
|      | 
 | ||||||
|     form_class = get_form(settings.PROPOSAL_FORMS[kind_slug]) |     form_class = get_form(settings.PROPOSAL_FORMS[kind_slug]) | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         form = form_class(request.POST) |         form = form_class(request.POST) | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|  | @ -79,7 +79,7 @@ def proposal_submit_kind(request, kind_slug): | ||||||
|             return redirect("dashboard") |             return redirect("dashboard") | ||||||
|     else: |     else: | ||||||
|         form = form_class() |         form = form_class() | ||||||
|      | 
 | ||||||
|     return render(request, "proposals/proposal_submit_kind.html", { |     return render(request, "proposals/proposal_submit_kind.html", { | ||||||
|         "kind": kind, |         "kind": kind, | ||||||
|         "form": form, |         "form": form, | ||||||
|  | @ -91,17 +91,17 @@ def proposal_speaker_manage(request, pk): | ||||||
|     queryset = ProposalBase.objects.select_related("speaker") |     queryset = ProposalBase.objects.select_related("speaker") | ||||||
|     proposal = get_object_or_404(queryset, pk=pk) |     proposal = get_object_or_404(queryset, pk=pk) | ||||||
|     proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) |     proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) | ||||||
|      | 
 | ||||||
|     if proposal.speaker != request.user.speaker_profile: |     if proposal.speaker != request.user.speaker_profile: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal) |         add_speaker_form = AddSpeakerForm(request.POST, proposal=proposal) | ||||||
|         if add_speaker_form.is_valid(): |         if add_speaker_form.is_valid(): | ||||||
|             message_ctx = { |             message_ctx = { | ||||||
|                 "proposal": proposal, |                 "proposal": proposal, | ||||||
|             } |             } | ||||||
|              | 
 | ||||||
|             def create_speaker_token(email_address): |             def create_speaker_token(email_address): | ||||||
|                 # create token and look for an existing speaker to prevent |                 # create token and look for an existing speaker to prevent | ||||||
|                 # duplicate tokens and confusing the pending speaker |                 # duplicate tokens and confusing the pending speaker | ||||||
|  | @ -135,13 +135,13 @@ def proposal_speaker_manage(request, pk): | ||||||
|                     # fire off email to user to create profile |                     # fire off email to user to create profile | ||||||
|                     send_email( |                     send_email( | ||||||
|                         [email_address], "speaker_no_profile", |                         [email_address], "speaker_no_profile", | ||||||
|                         context = message_ctx |                         context=message_ctx | ||||||
|                     ) |                     ) | ||||||
|                 else: |                 else: | ||||||
|                     # fire off email to user letting them they are loved. |                     # fire off email to user letting them they are loved. | ||||||
|                     send_email( |                     send_email( | ||||||
|                         [email_address], "speaker_addition", |                         [email_address], "speaker_addition", | ||||||
|                         context = message_ctx |                         context=message_ctx | ||||||
|                     ) |                     ) | ||||||
|             else: |             else: | ||||||
|                 speaker, token = create_speaker_token(email_address) |                 speaker, token = create_speaker_token(email_address) | ||||||
|  | @ -150,9 +150,10 @@ def proposal_speaker_manage(request, pk): | ||||||
|                 # account and speaker profile |                 # account and speaker profile | ||||||
|                 send_email( |                 send_email( | ||||||
|                     [email_address], "speaker_invite", |                     [email_address], "speaker_invite", | ||||||
|                     context = message_ctx |                     context=message_ctx | ||||||
|                 ) |                 ) | ||||||
|             invitation, created = AdditionalSpeaker.objects.get_or_create(proposalbase=proposal.proposalbase_ptr, speaker=speaker) |             invitation, created = AdditionalSpeaker.objects.get_or_create( | ||||||
|  |                 proposalbase=proposal.proposalbase_ptr, speaker=speaker) | ||||||
|             messages.success(request, "Speaker invited to proposal.") |             messages.success(request, "Speaker invited to proposal.") | ||||||
|             return redirect("proposal_speaker_manage", proposal.pk) |             return redirect("proposal_speaker_manage", proposal.pk) | ||||||
|     else: |     else: | ||||||
|  | @ -173,14 +174,14 @@ def proposal_edit(request, pk): | ||||||
| 
 | 
 | ||||||
|     if request.user != proposal.speaker.user: |     if request.user != proposal.speaker.user: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     if not proposal.can_edit(): |     if not proposal.can_edit(): | ||||||
|         ctx = { |         ctx = { | ||||||
|             "title": "Proposal editing closed", |             "title": "Proposal editing closed", | ||||||
|             "body": "Proposal editing is closed for this session type." |             "body": "Proposal editing is closed for this session type." | ||||||
|         } |         } | ||||||
|         return render(request, "proposals/proposal_error.html", ctx) |         return render(request, "proposals/proposal_error.html", ctx) | ||||||
|      | 
 | ||||||
|     form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug]) |     form_class = get_form(settings.PROPOSAL_FORMS[proposal.kind.slug]) | ||||||
| 
 | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|  | @ -206,7 +207,7 @@ def proposal_edit(request, pk): | ||||||
|             return redirect("proposal_detail", proposal.pk) |             return redirect("proposal_detail", proposal.pk) | ||||||
|     else: |     else: | ||||||
|         form = form_class(instance=proposal) |         form = form_class(instance=proposal) | ||||||
|      | 
 | ||||||
|     return render(request, "proposals/proposal_edit.html", { |     return render(request, "proposals/proposal_edit.html", { | ||||||
|         "proposal": proposal, |         "proposal": proposal, | ||||||
|         "form": form, |         "form": form, | ||||||
|  | @ -218,22 +219,22 @@ def proposal_detail(request, pk): | ||||||
|     queryset = ProposalBase.objects.select_related("speaker", "speaker__user") |     queryset = ProposalBase.objects.select_related("speaker", "speaker__user") | ||||||
|     proposal = get_object_or_404(queryset, pk=pk) |     proposal = get_object_or_404(queryset, pk=pk) | ||||||
|     proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) |     proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) | ||||||
|      | 
 | ||||||
|     if request.user not in [p.user for p in proposal.speakers()]: |     if request.user not in [p.user for p in proposal.speakers()]: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     if "symposion.reviews" in settings.INSTALLED_APPS: |     if "symposion.reviews" in settings.INSTALLED_APPS: | ||||||
|         from symposion.reviews.forms import SpeakerCommentForm |         from symposion.reviews.forms import SpeakerCommentForm | ||||||
|         message_form = SpeakerCommentForm() |         message_form = SpeakerCommentForm() | ||||||
|         if request.method == "POST": |         if request.method == "POST": | ||||||
|             message_form = SpeakerCommentForm(request.POST) |             message_form = SpeakerCommentForm(request.POST) | ||||||
|             if message_form.is_valid(): |             if message_form.is_valid(): | ||||||
|                  | 
 | ||||||
|                 message = message_form.save(commit=False) |                 message = message_form.save(commit=False) | ||||||
|                 message.user = request.user |                 message.user = request.user | ||||||
|                 message.proposal = proposal |                 message.proposal = proposal | ||||||
|                 message.save() |                 message.save() | ||||||
|                  | 
 | ||||||
|                 ProposalMessage = SpeakerCommentForm.Meta.model |                 ProposalMessage = SpeakerCommentForm.Meta.model | ||||||
|                 reviewers = User.objects.filter( |                 reviewers = User.objects.filter( | ||||||
|                     id__in=ProposalMessage.objects.filter( |                     id__in=ProposalMessage.objects.filter( | ||||||
|  | @ -242,7 +243,7 @@ def proposal_detail(request, pk): | ||||||
|                         user=request.user |                         user=request.user | ||||||
|                     ).distinct().values_list("user", flat=True) |                     ).distinct().values_list("user", flat=True) | ||||||
|                 ) |                 ) | ||||||
|                  | 
 | ||||||
|                 for reviewer in reviewers: |                 for reviewer in reviewers: | ||||||
|                     ctx = { |                     ctx = { | ||||||
|                         "proposal": proposal, |                         "proposal": proposal, | ||||||
|  | @ -253,13 +254,13 @@ def proposal_detail(request, pk): | ||||||
|                         [reviewer.email], "proposal_new_message", |                         [reviewer.email], "proposal_new_message", | ||||||
|                         context=ctx |                         context=ctx | ||||||
|                     ) |                     ) | ||||||
|                  | 
 | ||||||
|                 return redirect(request.path) |                 return redirect(request.path) | ||||||
|         else: |         else: | ||||||
|             message_form = SpeakerCommentForm() |             message_form = SpeakerCommentForm() | ||||||
|     else: |     else: | ||||||
|         message_form = None |         message_form = None | ||||||
|      | 
 | ||||||
|     return render(request, "proposals/proposal_detail.html", { |     return render(request, "proposals/proposal_detail.html", { | ||||||
|         "proposal": proposal, |         "proposal": proposal, | ||||||
|         "message_form": message_form |         "message_form": message_form | ||||||
|  | @ -271,7 +272,7 @@ def proposal_cancel(request, pk): | ||||||
|     queryset = ProposalBase.objects.select_related("speaker") |     queryset = ProposalBase.objects.select_related("speaker") | ||||||
|     proposal = get_object_or_404(queryset, pk=pk) |     proposal = get_object_or_404(queryset, pk=pk) | ||||||
|     proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) |     proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) | ||||||
|      | 
 | ||||||
|     if proposal.speaker.user != request.user: |     if proposal.speaker.user != request.user: | ||||||
|         return HttpResponseForbidden() |         return HttpResponseForbidden() | ||||||
| 
 | 
 | ||||||
|  | @ -281,7 +282,7 @@ def proposal_cancel(request, pk): | ||||||
|         # @@@ fire off email to submitter and other speakers |         # @@@ fire off email to submitter and other speakers | ||||||
|         messages.success(request, "%s has been cancelled" % proposal.title) |         messages.success(request, "%s has been cancelled" % proposal.title) | ||||||
|         return redirect("dashboard") |         return redirect("dashboard") | ||||||
|      | 
 | ||||||
|     return render(request, "proposals/proposal_cancel.html", { |     return render(request, "proposals/proposal_cancel.html", { | ||||||
|         "proposal": proposal, |         "proposal": proposal, | ||||||
|     }) |     }) | ||||||
|  | @ -311,7 +312,8 @@ def proposal_leave(request, pk): | ||||||
| @login_required | @login_required | ||||||
| def proposal_pending_join(request, pk): | def proposal_pending_join(request, pk): | ||||||
|     proposal = get_object_or_404(ProposalBase, pk=pk) |     proposal = get_object_or_404(ProposalBase, pk=pk) | ||||||
|     speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, proposalbase=proposal) |     speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, | ||||||
|  |                                  proposalbase=proposal) | ||||||
|     if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING: |     if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING: | ||||||
|         speaking.status = AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED |         speaking.status = AdditionalSpeaker.SPEAKING_STATUS_ACCEPTED | ||||||
|         speaking.save() |         speaking.save() | ||||||
|  | @ -324,7 +326,8 @@ def proposal_pending_join(request, pk): | ||||||
| @login_required | @login_required | ||||||
| def proposal_pending_decline(request, pk): | def proposal_pending_decline(request, pk): | ||||||
|     proposal = get_object_or_404(ProposalBase, pk=pk) |     proposal = get_object_or_404(ProposalBase, pk=pk) | ||||||
|     speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, proposalbase=proposal) |     speaking = get_object_or_404(AdditionalSpeaker, speaker=request.user.speaker_profile, | ||||||
|  |                                  proposalbase=proposal) | ||||||
|     if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING: |     if speaking.status == AdditionalSpeaker.SPEAKING_STATUS_PENDING: | ||||||
|         speaking.status = AdditionalSpeaker.SPEAKING_STATUS_DECLINED |         speaking.status = AdditionalSpeaker.SPEAKING_STATUS_DECLINED | ||||||
|         speaking.save() |         speaking.save() | ||||||
|  | @ -339,10 +342,10 @@ def document_create(request, proposal_pk): | ||||||
|     queryset = ProposalBase.objects.select_related("speaker") |     queryset = ProposalBase.objects.select_related("speaker") | ||||||
|     proposal = get_object_or_404(queryset, pk=proposal_pk) |     proposal = get_object_or_404(queryset, pk=proposal_pk) | ||||||
|     proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) |     proposal = ProposalBase.objects.get_subclass(pk=proposal.pk) | ||||||
|      | 
 | ||||||
|     if proposal.cancelled: |     if proposal.cancelled: | ||||||
|         return HttpResponseForbidden() |         return HttpResponseForbidden() | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         form = SupportingDocumentCreateForm(request.POST, request.FILES) |         form = SupportingDocumentCreateForm(request.POST, request.FILES) | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|  | @ -353,7 +356,7 @@ def document_create(request, proposal_pk): | ||||||
|             return redirect("proposal_detail", proposal.pk) |             return redirect("proposal_detail", proposal.pk) | ||||||
|     else: |     else: | ||||||
|         form = SupportingDocumentCreateForm() |         form = SupportingDocumentCreateForm() | ||||||
|          | 
 | ||||||
|     return render(request, "proposals/document_create.html", { |     return render(request, "proposals/document_create.html", { | ||||||
|         "proposal": proposal, |         "proposal": proposal, | ||||||
|         "form": form, |         "form": form, | ||||||
|  | @ -378,8 +381,8 @@ def document_download(request, pk, *args): | ||||||
| def document_delete(request, pk): | def document_delete(request, pk): | ||||||
|     document = get_object_or_404(SupportingDocument, pk=pk, uploaded_by=request.user) |     document = get_object_or_404(SupportingDocument, pk=pk, uploaded_by=request.user) | ||||||
|     proposal_pk = document.proposal.pk |     proposal_pk = document.proposal.pk | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         document.delete() |         document.delete() | ||||||
|      | 
 | ||||||
|     return redirect("proposal_detail", proposal_pk) |     return redirect("proposal_detail", proposal_pk) | ||||||
|  |  | ||||||
|  | @ -1,5 +1,3 @@ | ||||||
| from django.contrib.contenttypes.models import ContentType |  | ||||||
| 
 |  | ||||||
| from symposion.proposals.models import ProposalSection | from symposion.proposals.models import ProposalSection | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -9,13 +9,13 @@ class ReviewForm(forms.ModelForm): | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = Review |         model = Review | ||||||
|         fields = ["vote", "comment"] |         fields = ["vote", "comment"] | ||||||
|         widgets = { "comment": MarkItUpWidget() } |         widgets = {"comment": MarkItUpWidget()} | ||||||
|      | 
 | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         super(ReviewForm, self).__init__(*args, **kwargs) |         super(ReviewForm, self).__init__(*args, **kwargs) | ||||||
|         self.fields["vote"] = forms.ChoiceField( |         self.fields["vote"] = forms.ChoiceField( | ||||||
|             widget = forms.RadioSelect(), |             widget=forms.RadioSelect(), | ||||||
|             choices = VOTES.CHOICES |             choices=VOTES.CHOICES | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -23,14 +23,14 @@ class ReviewCommentForm(forms.ModelForm): | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = Comment |         model = Comment | ||||||
|         fields = ["text"] |         fields = ["text"] | ||||||
|         widgets = { "text": MarkItUpWidget() } |         widgets = {"text": MarkItUpWidget()} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SpeakerCommentForm(forms.ModelForm): | class SpeakerCommentForm(forms.ModelForm): | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = ProposalMessage |         model = ProposalMessage | ||||||
|         fields = ["message"] |         fields = ["message"] | ||||||
|         widgets = { "message": MarkItUpWidget() } |         widgets = {"message": MarkItUpWidget()} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BulkPresentationForm(forms.Form): | class BulkPresentationForm(forms.Form): | ||||||
|  |  | ||||||
|  | @ -1,8 +1,3 @@ | ||||||
| import csv |  | ||||||
| import os |  | ||||||
| import random |  | ||||||
| 
 |  | ||||||
| from django.contrib.auth import models |  | ||||||
| from django.core.management.base import BaseCommand | from django.core.management.base import BaseCommand | ||||||
| 
 | 
 | ||||||
| from symposion.reviews.models import ReviewAssignment | from symposion.reviews.models import ReviewAssignment | ||||||
|  |  | ||||||
|  | @ -1,11 +1,9 @@ | ||||||
| from django.core.management.base import BaseCommand | from django.core.management.base import BaseCommand | ||||||
| 
 | 
 | ||||||
| from django.contrib.auth.models import Group |  | ||||||
| 
 |  | ||||||
| from symposion.reviews.models import ProposalResult | from symposion.reviews.models import ProposalResult | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Command(BaseCommand): | class Command(BaseCommand): | ||||||
|      | 
 | ||||||
|     def handle(self, *args, **options): |     def handle(self, *args, **options): | ||||||
|         ProposalResult.full_calculate() |         ProposalResult.full_calculate() | ||||||
|  |  | ||||||
|  | @ -7,14 +7,14 @@ from symposion.proposals.models import ProposalSection | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Command(BaseCommand): | class Command(BaseCommand): | ||||||
|      | 
 | ||||||
|     def handle(self, *args, **options): |     def handle(self, *args, **options): | ||||||
|         ct, created = ContentType.objects.get_or_create( |         ct, created = ContentType.objects.get_or_create( | ||||||
|             model="", |             model="", | ||||||
|             app_label="reviews", |             app_label="reviews", | ||||||
|             defaults={"name": "reviews"} |             defaults={"name": "reviews"} | ||||||
|         ) |         ) | ||||||
|          | 
 | ||||||
|         for ps in ProposalSection.objects.all(): |         for ps in ProposalSection.objects.all(): | ||||||
|             for action in ["review", "manage"]: |             for action in ["review", "manage"]: | ||||||
|                 perm, created = Permission.objects.get_or_create( |                 perm, created = Permission.objects.get_or_create( | ||||||
|  |  | ||||||
|  | @ -5,11 +5,12 @@ from symposion.reviews.models import ProposalResult, promote_proposal | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Command(BaseCommand): | class Command(BaseCommand): | ||||||
|      | 
 | ||||||
|     def handle(self, *args, **options): |     def handle(self, *args, **options): | ||||||
|         accepted_proposals = ProposalResult.objects.filter(status="accepted") |         accepted_proposals = ProposalResult.objects.filter(status="accepted") | ||||||
|         accepted_proposals = accepted_proposals.order_by("proposal") |         accepted_proposals = accepted_proposals.order_by("proposal") | ||||||
|          | 
 | ||||||
|         for result in accepted_proposals: |         for result in accepted_proposals: | ||||||
|             promote_proposal(result.proposal) |             promote_proposal(result.proposal) | ||||||
|         connections["default"].cursor().execute("SELECT setval('schedule_session_id_seq', (SELECT max(id) FROM schedule_session))") |         connections["default"].cursor().execute( | ||||||
|  |             "SELECT setval('schedule_session_id_seq', (SELECT max(id) FROM schedule_session))") | ||||||
|  |  | ||||||
|  | @ -15,11 +15,11 @@ from symposion.schedule.models import Presentation | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ProposalScoreExpression(object): | class ProposalScoreExpression(object): | ||||||
|      | 
 | ||||||
|     def as_sql(self, qn, connection=None): |     def as_sql(self, qn, connection=None): | ||||||
|         sql = "((3 * plus_one + plus_zero) - (minus_zero + 3 * minus_one))" |         sql = "((3 * plus_one + plus_zero) - (minus_zero + 3 * minus_one))" | ||||||
|         return sql, [] |         return sql, [] | ||||||
|      | 
 | ||||||
|     def prepare_database_save(self, unused): |     def prepare_database_save(self, unused): | ||||||
|         return self |         return self | ||||||
| 
 | 
 | ||||||
|  | @ -29,7 +29,7 @@ class Votes(object): | ||||||
|     PLUS_ZERO = "+0" |     PLUS_ZERO = "+0" | ||||||
|     MINUS_ZERO = u"−0" |     MINUS_ZERO = u"−0" | ||||||
|     MINUS_ONE = u"−1" |     MINUS_ONE = u"−1" | ||||||
|      | 
 | ||||||
|     CHOICES = [ |     CHOICES = [ | ||||||
|         (PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."), |         (PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."), | ||||||
|         (PLUS_ZERO, u"+0 — OK proposal, but I will not argue for it to be accepted."), |         (PLUS_ZERO, u"+0 — OK proposal, but I will not argue for it to be accepted."), | ||||||
|  | @ -45,21 +45,21 @@ class ReviewAssignment(models.Model): | ||||||
|     AUTO_ASSIGNED_LATER = 2 |     AUTO_ASSIGNED_LATER = 2 | ||||||
| 
 | 
 | ||||||
|     NUM_REVIEWERS = 3 |     NUM_REVIEWERS = 3 | ||||||
|      | 
 | ||||||
|     ORIGIN_CHOICES = [ |     ORIGIN_CHOICES = [ | ||||||
|         (AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"), |         (AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"), | ||||||
|         (OPT_IN, "opted-in"), |         (OPT_IN, "opted-in"), | ||||||
|         (AUTO_ASSIGNED_LATER, "auto-assigned, later"), |         (AUTO_ASSIGNED_LATER, "auto-assigned, later"), | ||||||
|     ] |     ] | ||||||
|      | 
 | ||||||
|     proposal = models.ForeignKey("proposals.ProposalBase") |     proposal = models.ForeignKey("proposals.ProposalBase") | ||||||
|     user = models.ForeignKey(User) |     user = models.ForeignKey(User) | ||||||
|      | 
 | ||||||
|     origin = models.IntegerField(choices=ORIGIN_CHOICES) |     origin = models.IntegerField(choices=ORIGIN_CHOICES) | ||||||
|      | 
 | ||||||
|     assigned_at = models.DateTimeField(default=datetime.now) |     assigned_at = models.DateTimeField(default=datetime.now) | ||||||
|     opted_out = models.BooleanField() |     opted_out = models.BooleanField() | ||||||
|      | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL): |     def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL): | ||||||
|         speakers = [proposal.speaker] + list(proposal.additional_speakers.all()) |         speakers = [proposal.speaker] + list(proposal.additional_speakers.all()) | ||||||
|  | @ -94,7 +94,7 @@ class ReviewAssignment(models.Model): | ||||||
| class ProposalMessage(models.Model): | class ProposalMessage(models.Model): | ||||||
|     proposal = models.ForeignKey("proposals.ProposalBase", related_name="messages") |     proposal = models.ForeignKey("proposals.ProposalBase", related_name="messages") | ||||||
|     user = models.ForeignKey(User) |     user = models.ForeignKey(User) | ||||||
|      | 
 | ||||||
|     message = MarkupField() |     message = MarkupField() | ||||||
|     submitted_at = models.DateTimeField(default=datetime.now, editable=False) |     submitted_at = models.DateTimeField(default=datetime.now, editable=False) | ||||||
| 
 | 
 | ||||||
|  | @ -104,24 +104,24 @@ class ProposalMessage(models.Model): | ||||||
| 
 | 
 | ||||||
| class Review(models.Model): | class Review(models.Model): | ||||||
|     VOTES = VOTES |     VOTES = VOTES | ||||||
|      | 
 | ||||||
|     proposal = models.ForeignKey("proposals.ProposalBase", related_name="reviews") |     proposal = models.ForeignKey("proposals.ProposalBase", related_name="reviews") | ||||||
|     user = models.ForeignKey(User) |     user = models.ForeignKey(User) | ||||||
|      | 
 | ||||||
|     # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel |     # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel | ||||||
|     # like some complicated encoding system. |     # like some complicated encoding system. | ||||||
|     vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES) |     vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES) | ||||||
|     comment = MarkupField() |     comment = MarkupField() | ||||||
|     submitted_at = models.DateTimeField(default=datetime.now, editable=False) |     submitted_at = models.DateTimeField(default=datetime.now, editable=False) | ||||||
|      | 
 | ||||||
|     def save(self, **kwargs): |     def save(self, **kwargs): | ||||||
|         if self.vote: |         if self.vote: | ||||||
|             vote, created = LatestVote.objects.get_or_create( |             vote, created = LatestVote.objects.get_or_create( | ||||||
|                 proposal = self.proposal, |                 proposal=self.proposal, | ||||||
|                 user = self.user, |                 user=self.user, | ||||||
|                 defaults = dict( |                 defaults=dict( | ||||||
|                     vote = self.vote, |                     vote=self.vote, | ||||||
|                     submitted_at = self.submitted_at, |                     submitted_at=self.submitted_at, | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
|             if not created: |             if not created: | ||||||
|  | @ -130,7 +130,7 @@ class Review(models.Model): | ||||||
|             else: |             else: | ||||||
|                 self.proposal.result.update_vote(self.vote) |                 self.proposal.result.update_vote(self.vote) | ||||||
|         super(Review, self).save(**kwargs) |         super(Review, self).save(**kwargs) | ||||||
|      | 
 | ||||||
|     def delete(self): |     def delete(self): | ||||||
|         model = self.__class__ |         model = self.__class__ | ||||||
|         user_reviews = model._default_manager.filter( |         user_reviews = model._default_manager.filter( | ||||||
|  | @ -152,7 +152,8 @@ class Review(models.Model): | ||||||
|             if self == latest: |             if self == latest: | ||||||
|                 # self is the latest review; revert the latest vote to the |                 # self is the latest review; revert the latest vote to the | ||||||
|                 # previous vote |                 # previous vote | ||||||
|                 previous = user_reviews.filter(submitted_at__lt=self.submitted_at).order_by("-submitted_at")[0] |                 previous = user_reviews.filter(submitted_at__lt=self.submitted_at)\ | ||||||
|  |                     .order_by("-submitted_at")[0] | ||||||
|                 self.proposal.result.update_vote(self.vote, previous=previous.vote, removal=True) |                 self.proposal.result.update_vote(self.vote, previous=previous.vote, removal=True) | ||||||
|                 lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user) |                 lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user) | ||||||
|                 lv.update( |                 lv.update( | ||||||
|  | @ -166,7 +167,7 @@ class Review(models.Model): | ||||||
|                 self.proposal.result.save() |                 self.proposal.result.save() | ||||||
|         # in all cases we need to delete the review; let's do it! |         # in all cases we need to delete the review; let's do it! | ||||||
|         super(Review, self).delete() |         super(Review, self).delete() | ||||||
|      | 
 | ||||||
|     def css_class(self): |     def css_class(self): | ||||||
|         return { |         return { | ||||||
|             self.VOTES.PLUS_ONE: "plus-one", |             self.VOTES.PLUS_ONE: "plus-one", | ||||||
|  | @ -174,7 +175,7 @@ class Review(models.Model): | ||||||
|             self.VOTES.MINUS_ZERO: "minus-zero", |             self.VOTES.MINUS_ZERO: "minus-zero", | ||||||
|             self.VOTES.MINUS_ONE: "minus-one", |             self.VOTES.MINUS_ONE: "minus-one", | ||||||
|         }[self.vote] |         }[self.vote] | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def section(self): |     def section(self): | ||||||
|         return self.proposal.kind.section.slug |         return self.proposal.kind.section.slug | ||||||
|  | @ -182,18 +183,18 @@ class Review(models.Model): | ||||||
| 
 | 
 | ||||||
| class LatestVote(models.Model): | class LatestVote(models.Model): | ||||||
|     VOTES = VOTES |     VOTES = VOTES | ||||||
|      | 
 | ||||||
|     proposal = models.ForeignKey("proposals.ProposalBase", related_name="votes") |     proposal = models.ForeignKey("proposals.ProposalBase", related_name="votes") | ||||||
|     user = models.ForeignKey(User) |     user = models.ForeignKey(User) | ||||||
|      | 
 | ||||||
|     # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel |     # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel | ||||||
|     # like some complicated encoding system. |     # like some complicated encoding system. | ||||||
|     vote = models.CharField(max_length=2, choices=VOTES.CHOICES) |     vote = models.CharField(max_length=2, choices=VOTES.CHOICES) | ||||||
|     submitted_at = models.DateTimeField(default=datetime.now, editable=False) |     submitted_at = models.DateTimeField(default=datetime.now, editable=False) | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         unique_together = [("proposal", "user")] |         unique_together = [("proposal", "user")] | ||||||
|      | 
 | ||||||
|     def css_class(self): |     def css_class(self): | ||||||
|         return { |         return { | ||||||
|             self.VOTES.PLUS_ONE: "plus-one", |             self.VOTES.PLUS_ONE: "plus-one", | ||||||
|  | @ -223,7 +224,7 @@ class ProposalResult(models.Model): | ||||||
|         ("undecided", "undecided"), |         ("undecided", "undecided"), | ||||||
|         ("standby", "standby"), |         ("standby", "standby"), | ||||||
|     ], default="undecided") |     ], default="undecided") | ||||||
|      | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def full_calculate(cls): |     def full_calculate(cls): | ||||||
|         for proposal in ProposalBase.objects.all(): |         for proposal in ProposalBase.objects.all(): | ||||||
|  | @ -231,24 +232,24 @@ class ProposalResult(models.Model): | ||||||
|             result.comment_count = Review.objects.filter(proposal=proposal).count() |             result.comment_count = Review.objects.filter(proposal=proposal).count() | ||||||
|             result.vote_count = LatestVote.objects.filter(proposal=proposal).count() |             result.vote_count = LatestVote.objects.filter(proposal=proposal).count() | ||||||
|             result.plus_one = LatestVote.objects.filter( |             result.plus_one = LatestVote.objects.filter( | ||||||
|                 proposal = proposal, |                 proposal=proposal, | ||||||
|                 vote = VOTES.PLUS_ONE |                 vote=VOTES.PLUS_ONE | ||||||
|             ).count() |             ).count() | ||||||
|             result.plus_zero = LatestVote.objects.filter( |             result.plus_zero = LatestVote.objects.filter( | ||||||
|                 proposal = proposal, |                 proposal=proposal, | ||||||
|                 vote = VOTES.PLUS_ZERO |                 vote=VOTES.PLUS_ZERO | ||||||
|             ).count() |             ).count() | ||||||
|             result.minus_zero = LatestVote.objects.filter( |             result.minus_zero = LatestVote.objects.filter( | ||||||
|                 proposal = proposal, |                 proposal=proposal, | ||||||
|                 vote = VOTES.MINUS_ZERO |                 vote=VOTES.MINUS_ZERO | ||||||
|             ).count() |             ).count() | ||||||
|             result.minus_one = LatestVote.objects.filter( |             result.minus_one = LatestVote.objects.filter( | ||||||
|                 proposal = proposal, |                 proposal=proposal, | ||||||
|                 vote = VOTES.MINUS_ONE |                 vote=VOTES.MINUS_ONE | ||||||
|             ).count() |             ).count() | ||||||
|             result.save() |             result.save() | ||||||
|             cls._default_manager.filter(pk=result.pk).update(score=ProposalScoreExpression()) |             cls._default_manager.filter(pk=result.pk).update(score=ProposalScoreExpression()) | ||||||
|      | 
 | ||||||
|     def update_vote(self, vote, previous=None, removal=False): |     def update_vote(self, vote, previous=None, removal=False): | ||||||
|         mapping = { |         mapping = { | ||||||
|             VOTES.PLUS_ONE: "plus_one", |             VOTES.PLUS_ONE: "plus_one", | ||||||
|  | @ -283,7 +284,7 @@ class Comment(models.Model): | ||||||
|     proposal = models.ForeignKey("proposals.ProposalBase", related_name="comments") |     proposal = models.ForeignKey("proposals.ProposalBase", related_name="comments") | ||||||
|     commenter = models.ForeignKey(User) |     commenter = models.ForeignKey(User) | ||||||
|     text = MarkupField() |     text = MarkupField() | ||||||
|      | 
 | ||||||
|     # Or perhaps more accurately, can the user see this comment. |     # Or perhaps more accurately, can the user see this comment. | ||||||
|     public = models.BooleanField(choices=[ |     public = models.BooleanField(choices=[ | ||||||
|         (True, "public"), |         (True, "public"), | ||||||
|  | @ -293,7 +294,7 @@ class Comment(models.Model): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class NotificationTemplate(models.Model): | class NotificationTemplate(models.Model): | ||||||
|      | 
 | ||||||
|     label = models.CharField(max_length=100) |     label = models.CharField(max_length=100) | ||||||
|     from_address = models.EmailField() |     from_address = models.EmailField() | ||||||
|     subject = models.CharField(max_length=100) |     subject = models.CharField(max_length=100) | ||||||
|  | @ -301,15 +302,16 @@ class NotificationTemplate(models.Model): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ResultNotification(models.Model): | class ResultNotification(models.Model): | ||||||
|      | 
 | ||||||
|     proposal = models.ForeignKey("proposals.ProposalBase", related_name="notifications") |     proposal = models.ForeignKey("proposals.ProposalBase", related_name="notifications") | ||||||
|     template = models.ForeignKey(NotificationTemplate, null=True, blank=True, on_delete=models.SET_NULL) |     template = models.ForeignKey(NotificationTemplate, null=True, blank=True, | ||||||
|  |                                  on_delete=models.SET_NULL) | ||||||
|     timestamp = models.DateTimeField(default=datetime.now) |     timestamp = models.DateTimeField(default=datetime.now) | ||||||
|     to_address = models.EmailField() |     to_address = models.EmailField() | ||||||
|     from_address = models.EmailField() |     from_address = models.EmailField() | ||||||
|     subject = models.CharField(max_length=100) |     subject = models.CharField(max_length=100) | ||||||
|     body = models.TextField() |     body = models.TextField() | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def email_args(self): |     def email_args(self): | ||||||
|         return (self.subject, self.body, self.from_address, [self.to_address]) |         return (self.subject, self.body, self.from_address, [self.to_address]) | ||||||
|  | @ -321,18 +323,18 @@ def promote_proposal(proposal): | ||||||
|         presentation = proposal.presentation |         presentation = proposal.presentation | ||||||
|     else: |     else: | ||||||
|         presentation = Presentation( |         presentation = Presentation( | ||||||
|             title = proposal.title, |             title=proposal.title, | ||||||
|             description = proposal.description, |             description=proposal.description, | ||||||
|             abstract = proposal.abstract, |             abstract=proposal.abstract, | ||||||
|             speaker = proposal.speaker, |             speaker=proposal.speaker, | ||||||
|             section = proposal.section, |             section=proposal.section, | ||||||
|             proposal_base = proposal, |             proposal_base=proposal, | ||||||
|         ) |         ) | ||||||
|         presentation.save() |         presentation.save() | ||||||
|         for speaker in proposal.additional_speakers.all(): |         for speaker in proposal.additional_speakers.all(): | ||||||
|             presentation.additional_speakers.add(speaker) |             presentation.additional_speakers.add(speaker) | ||||||
|             presentation.save() |             presentation.save() | ||||||
|      | 
 | ||||||
|     return presentation |     return presentation | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| from django import template | from django import template | ||||||
| 
 | 
 | ||||||
| from symposion.reviews.models import Review, ReviewAssignment | from symposion.reviews.models import ReviewAssignment | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| register = template.Library() | register = template.Library() | ||||||
|  |  | ||||||
|  | @ -15,44 +15,44 @@ class login(object): | ||||||
|             success, |             success, | ||||||
|             "login with username=%r, password=%r failed" % (user, password) |             "login with username=%r, password=%r failed" % (user, password) | ||||||
|         ) |         ) | ||||||
|      | 
 | ||||||
|     def __enter__(self): |     def __enter__(self): | ||||||
|         pass |         pass | ||||||
|      | 
 | ||||||
|     def __exit__(self, *args): |     def __exit__(self, *args): | ||||||
|         self.testcase.client.logout() |         self.testcase.client.logout() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ReviewTests(TestCase): | class ReviewTests(TestCase): | ||||||
|     fixtures = ["proposals"] |     fixtures = ["proposals"] | ||||||
|      | 
 | ||||||
|     def get(self, url_name, *args, **kwargs): |     def get(self, url_name, *args, **kwargs): | ||||||
|         return self.client.get(reverse(url_name, args=args, kwargs=kwargs)) |         return self.client.get(reverse(url_name, args=args, kwargs=kwargs)) | ||||||
|      | 
 | ||||||
|     def post(self, url_name, *args, **kwargs): |     def post(self, url_name, *args, **kwargs): | ||||||
|         data = kwargs.pop("data") |         data = kwargs.pop("data") | ||||||
|         return self.client.post(reverse(url_name, args=args, kwargs=kwargs), data) |         return self.client.post(reverse(url_name, args=args, kwargs=kwargs), data) | ||||||
|      | 
 | ||||||
|     def login(self, user, password): |     def login(self, user, password): | ||||||
|         return login(self, user, password) |         return login(self, user, password) | ||||||
|      | 
 | ||||||
|     def test_detail_perms(self): |     def test_detail_perms(self): | ||||||
|         guidos_proposal = Proposal.objects.all()[0] |         guidos_proposal = Proposal.objects.all()[0] | ||||||
|         response = self.get("review_detail", pk=guidos_proposal.pk) |         response = self.get("review_detail", pk=guidos_proposal.pk) | ||||||
|          | 
 | ||||||
|         # Not logged in |         # Not logged in | ||||||
|         self.assertEqual(response.status_code, 302) |         self.assertEqual(response.status_code, 302) | ||||||
|          | 
 | ||||||
|         with self.login("guido", "pythonisawesome"): |         with self.login("guido", "pythonisawesome"): | ||||||
|             response = self.get("review_detail", pk=guidos_proposal.pk) |             response = self.get("review_detail", pk=guidos_proposal.pk) | ||||||
|             # Guido can see his own proposal. |             # Guido can see his own proposal. | ||||||
|             self.assertEqual(response.status_code, 200) |             self.assertEqual(response.status_code, 200) | ||||||
|          | 
 | ||||||
|         with self.login("matz", "pythonsucks"): |         with self.login("matz", "pythonsucks"): | ||||||
|             response = self.get("review_detail", pk=guidos_proposal.pk) |             response = self.get("review_detail", pk=guidos_proposal.pk) | ||||||
|             # Matz can't see guido's proposal |             # Matz can't see guido's proposal | ||||||
|             self.assertEqual(response.status_code, 302) |             self.assertEqual(response.status_code, 302) | ||||||
|          | 
 | ||||||
|         larry = User.objects.get(username="larryw") |         larry = User.objects.get(username="larryw") | ||||||
|         # Larry is a trustworthy guy, he's a reviewer. |         # Larry is a trustworthy guy, he's a reviewer. | ||||||
|         larry.groups.add(Group.objects.get(name="reviewers")) |         larry.groups.add(Group.objects.get(name="reviewers")) | ||||||
|  | @ -60,10 +60,10 @@ class ReviewTests(TestCase): | ||||||
|             response = self.get("review_detail", pk=guidos_proposal.pk) |             response = self.get("review_detail", pk=guidos_proposal.pk) | ||||||
|             # Reviewers can see a review detail page. |             # Reviewers can see a review detail page. | ||||||
|             self.assertEqual(response.status_code, 200) |             self.assertEqual(response.status_code, 200) | ||||||
|      | 
 | ||||||
|     def test_reviewing(self): |     def test_reviewing(self): | ||||||
|         guidos_proposal = Proposal.objects.all()[0] |         guidos_proposal = Proposal.objects.all()[0] | ||||||
|          | 
 | ||||||
|         with self.login("guido", "pythonisawesome"): |         with self.login("guido", "pythonisawesome"): | ||||||
|             response = self.post("review_review", pk=guidos_proposal.pk, data={ |             response = self.post("review_review", pk=guidos_proposal.pk, data={ | ||||||
|                 "vote": "+1", |                 "vote": "+1", | ||||||
|  | @ -72,7 +72,7 @@ class ReviewTests(TestCase): | ||||||
|             self.assertEqual(response.status_code, 302) |             self.assertEqual(response.status_code, 302) | ||||||
|             # ... no vote recorded |             # ... no vote recorded | ||||||
|             self.assertEqual(guidos_proposal.reviews.count(), 0) |             self.assertEqual(guidos_proposal.reviews.count(), 0) | ||||||
|          | 
 | ||||||
|         larry = User.objects.get(username="larryw") |         larry = User.objects.get(username="larryw") | ||||||
|         # Larry is a trustworthy guy, he's a reviewer. |         # Larry is a trustworthy guy, he's a reviewer. | ||||||
|         larry.groups.add(Group.objects.get(name="reviewers")) |         larry.groups.add(Group.objects.get(name="reviewers")) | ||||||
|  | @ -90,7 +90,7 @@ class ReviewTests(TestCase): | ||||||
|             self.assertEqual(guidos_proposal.comments.count(), 1) |             self.assertEqual(guidos_proposal.comments.count(), 1) | ||||||
|             comment = guidos_proposal.comments.get() |             comment = guidos_proposal.comments.get() | ||||||
|             self.assertFalse(comment.public) |             self.assertFalse(comment.public) | ||||||
|              | 
 | ||||||
|             response = self.post("review_review", pk=guidos_proposal.pk, data={ |             response = self.post("review_review", pk=guidos_proposal.pk, data={ | ||||||
|                 "vote": "+1", |                 "vote": "+1", | ||||||
|                 "text": "Actually Perl is dead, we really need a talk on the future", |                 "text": "Actually Perl is dead, we really need a talk on the future", | ||||||
|  | @ -100,21 +100,21 @@ class ReviewTests(TestCase): | ||||||
|             assignment = ReviewAssignment.objects.get() |             assignment = ReviewAssignment.objects.get() | ||||||
|             self.assertEqual(assignment.review, Review.objects.order_by("-id")[0]) |             self.assertEqual(assignment.review, Review.objects.order_by("-id")[0]) | ||||||
|             self.assertEqual(guidos_proposal.comments.count(), 2) |             self.assertEqual(guidos_proposal.comments.count(), 2) | ||||||
|              | 
 | ||||||
|             # Larry's a big fan... |             # Larry's a big fan... | ||||||
|             response = self.post("review_review", pk=guidos_proposal.pk, data={ |             response = self.post("review_review", pk=guidos_proposal.pk, data={ | ||||||
|                 "vote": "+20", |                 "vote": "+20", | ||||||
|             }) |             }) | ||||||
|             self.assertEqual(guidos_proposal.reviews.count(), 2) |             self.assertEqual(guidos_proposal.reviews.count(), 2) | ||||||
|      | 
 | ||||||
|     def test_speaker_commenting(self): |     def test_speaker_commenting(self): | ||||||
|         guidos_proposal = Proposal.objects.all()[0] |         guidos_proposal = Proposal.objects.all()[0] | ||||||
|          | 
 | ||||||
|         with self.login("guido", "pythonisawesome"): |         with self.login("guido", "pythonisawesome"): | ||||||
|             response = self.get("review_comment", pk=guidos_proposal.pk) |             response = self.get("review_comment", pk=guidos_proposal.pk) | ||||||
|             # Guido can comment on his proposal. |             # Guido can comment on his proposal. | ||||||
|             self.assertEqual(response.status_code, 200) |             self.assertEqual(response.status_code, 200) | ||||||
|              | 
 | ||||||
|             response = self.post("review_comment", pk=guidos_proposal.pk, data={ |             response = self.post("review_comment", pk=guidos_proposal.pk, data={ | ||||||
|                 "text": "FYI I can do this as a 30-minute or 45-minute talk.", |                 "text": "FYI I can do this as a 30-minute or 45-minute talk.", | ||||||
|             }) |             }) | ||||||
|  | @ -122,7 +122,7 @@ class ReviewTests(TestCase): | ||||||
|             self.assertEqual(guidos_proposal.comments.count(), 1) |             self.assertEqual(guidos_proposal.comments.count(), 1) | ||||||
|             comment = guidos_proposal.comments.get() |             comment = guidos_proposal.comments.get() | ||||||
|             self.assertTrue(comment.public) |             self.assertTrue(comment.public) | ||||||
|          | 
 | ||||||
|         larry = User.objects.get(username="larryw") |         larry = User.objects.get(username="larryw") | ||||||
|         # Larry is a trustworthy guy, he's a reviewer. |         # Larry is a trustworthy guy, he's a reviewer. | ||||||
|         larry.groups.add(Group.objects.get(name="reviewers")) |         larry.groups.add(Group.objects.get(name="reviewers")) | ||||||
|  | @ -130,13 +130,13 @@ class ReviewTests(TestCase): | ||||||
|             response = self.get("review_comment", pk=guidos_proposal.pk) |             response = self.get("review_comment", pk=guidos_proposal.pk) | ||||||
|             # Larry can comment, since he's a reviewer |             # Larry can comment, since he's a reviewer | ||||||
|             self.assertEqual(response.status_code, 200) |             self.assertEqual(response.status_code, 200) | ||||||
|              | 
 | ||||||
|             response = self.post("review_comment", pk=guidos_proposal.pk, data={ |             response = self.post("review_comment", pk=guidos_proposal.pk, data={ | ||||||
|                 "text": "Thanks for the heads-up Guido." |                 "text": "Thanks for the heads-up Guido." | ||||||
|             }) |             }) | ||||||
|             self.assertEqual(response.status_code, 302) |             self.assertEqual(response.status_code, 302) | ||||||
|             self.assertEqual(guidos_proposal.comments.count(), 2) |             self.assertEqual(guidos_proposal.comments.count(), 2) | ||||||
|          | 
 | ||||||
|         with self.login("matz", "pythonsucks"): |         with self.login("matz", "pythonsucks"): | ||||||
|             response = self.get("review_comment", pk=guidos_proposal.pk) |             response = self.get("review_comment", pk=guidos_proposal.pk) | ||||||
|             # Matz can't comment. |             # Matz can't comment. | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # flake8: noqa | ||||||
| from django.conf.urls.defaults import patterns, url | from django.conf.urls.defaults import patterns, url | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -14,9 +15,9 @@ urlpatterns = patterns("symposion.reviews.views", | ||||||
|     url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/$", "result_notification", name="result_notification"), |     url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/$", "result_notification", name="result_notification"), | ||||||
|     url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/prepare/$", "result_notification_prepare", name="result_notification_prepare"), |     url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/prepare/$", "result_notification_prepare", name="result_notification_prepare"), | ||||||
|     url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/send/$", "result_notification_send", name="result_notification_send"), |     url(r"^section/(?P<section_slug>[\w\-]+)/notification/(?P<status>\w+)/send/$", "result_notification_send", name="result_notification_send"), | ||||||
|      | 
 | ||||||
|     url(r"^review/(?P<pk>\d+)/$", "review_detail", name="review_detail"), |     url(r"^review/(?P<pk>\d+)/$", "review_detail", name="review_detail"), | ||||||
|      | 
 | ||||||
|     url(r"^(?P<pk>\d+)/delete/$", "review_delete", name="review_delete"), |     url(r"^(?P<pk>\d+)/delete/$", "review_delete", name="review_delete"), | ||||||
|     url(r"^assignments/$", "review_assignments", name="review_assignments"), |     url(r"^assignments/$", "review_assignments", name="review_assignments"), | ||||||
|     url(r"^assignment/(?P<pk>\d+)/opt-out/$", "review_assignment_opt_out", name="review_assignment_opt_out"), |     url(r"^assignment/(?P<pk>\d+)/opt-out/$", "review_assignment_opt_out", name="review_assignment_opt_out"), | ||||||
|  |  | ||||||
|  | @ -2,16 +2,16 @@ def has_permission(user, proposal, speaker=False, reviewer=False): | ||||||
|     """ |     """ | ||||||
|     Returns whether or not ther user has permission to review this proposal, |     Returns whether or not ther user has permission to review this proposal, | ||||||
|     with the specified requirements. |     with the specified requirements. | ||||||
|      | 
 | ||||||
|     If ``speaker`` is ``True`` then the user can be one of the speakers for the  |     If ``speaker`` is ``True`` then the user can be one of the speakers for the | ||||||
|     proposal.  If ``reviewer`` is ``True`` the speaker can be a part of the |     proposal.  If ``reviewer`` is ``True`` the speaker can be a part of the | ||||||
|     reviewer group. |     reviewer group. | ||||||
|     """ |     """ | ||||||
|     if user.is_superuser: |     if user.is_superuser: | ||||||
|         return True |         return True | ||||||
|     if speaker: |     if speaker: | ||||||
|         if (user == proposal.speaker.user or  |         if user == proposal.speaker.user or \ | ||||||
|             proposal.additional_speakers.filter(user=user).exists()): |            proposal.additional_speakers.filter(user=user).exists(): | ||||||
|             return True |             return True | ||||||
|     if reviewer: |     if reviewer: | ||||||
|         if user.groups.filter(name="reviewers").exists(): |         if user.groups.filter(name="reviewers").exists(): | ||||||
|  |  | ||||||
|  | @ -1,5 +1,3 @@ | ||||||
| import re |  | ||||||
| 
 |  | ||||||
| from django.core.mail import send_mass_mail | from django.core.mail import send_mass_mail | ||||||
| from django.db.models import Q | from django.db.models import Q | ||||||
| from django.http import HttpResponseBadRequest, HttpResponseNotAllowed | from django.http import HttpResponseBadRequest, HttpResponseNotAllowed | ||||||
|  | @ -27,18 +25,18 @@ def access_not_permitted(request): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def proposals_generator(request, queryset, user_pk=None, check_speaker=True): | def proposals_generator(request, queryset, user_pk=None, check_speaker=True): | ||||||
|      | 
 | ||||||
|     for obj in queryset: |     for obj in queryset: | ||||||
|         # @@@ this sucks; we can do better |         # @@@ this sucks; we can do better | ||||||
|         if check_speaker: |         if check_speaker: | ||||||
|             if request.user in [s.user for s in obj.speakers()]: |             if request.user in [s.user for s in obj.speakers()]: | ||||||
|                 continue |                 continue | ||||||
|          | 
 | ||||||
|         try: |         try: | ||||||
|             obj.result |             obj.result | ||||||
|         except ProposalResult.DoesNotExist: |         except ProposalResult.DoesNotExist: | ||||||
|             ProposalResult.objects.get_or_create(proposal=obj) |             ProposalResult.objects.get_or_create(proposal=obj) | ||||||
|          | 
 | ||||||
|         obj.comment_count = obj.result.comment_count |         obj.comment_count = obj.result.comment_count | ||||||
|         obj.total_votes = obj.result.vote_count |         obj.total_votes = obj.result.vote_count | ||||||
|         obj.plus_one = obj.result.plus_one |         obj.plus_one = obj.result.plus_one | ||||||
|  | @ -46,37 +44,38 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True): | ||||||
|         obj.minus_zero = obj.result.minus_zero |         obj.minus_zero = obj.result.minus_zero | ||||||
|         obj.minus_one = obj.result.minus_one |         obj.minus_one = obj.result.minus_one | ||||||
|         lookup_params = dict(proposal=obj) |         lookup_params = dict(proposal=obj) | ||||||
|          | 
 | ||||||
|         if user_pk: |         if user_pk: | ||||||
|             lookup_params["user__pk"] = user_pk |             lookup_params["user__pk"] = user_pk | ||||||
|         else: |         else: | ||||||
|             lookup_params["user"] = request.user |             lookup_params["user"] = request.user | ||||||
|          | 
 | ||||||
|         try: |         try: | ||||||
|             obj.user_vote = LatestVote.objects.get(**lookup_params).vote |             obj.user_vote = LatestVote.objects.get(**lookup_params).vote | ||||||
|             obj.user_vote_css = LatestVote.objects.get(**lookup_params).css_class() |             obj.user_vote_css = LatestVote.objects.get(**lookup_params).css_class() | ||||||
|         except LatestVote.DoesNotExist: |         except LatestVote.DoesNotExist: | ||||||
|             obj.user_vote = None |             obj.user_vote = None | ||||||
|             obj.user_vote_css = "no-vote" |             obj.user_vote_css = "no-vote" | ||||||
|          | 
 | ||||||
|         yield obj |         yield obj | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has yet to review | # Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has | ||||||
| # depending on the link user clicks in dashboard | # yet to review depending on the link user clicks in dashboard | ||||||
| @login_required | @login_required | ||||||
| def review_section(request, section_slug, assigned=False, reviewed="all"): | def review_section(request, section_slug, assigned=False, reviewed="all"): | ||||||
|      | 
 | ||||||
|     if not request.user.has_perm("reviews.can_review_%s" % section_slug): |     if not request.user.has_perm("reviews.can_review_%s" % section_slug): | ||||||
|         return access_not_permitted(request) |         return access_not_permitted(request) | ||||||
|      | 
 | ||||||
|     section = get_object_or_404(ProposalSection, section__slug=section_slug) |     section = get_object_or_404(ProposalSection, section__slug=section_slug) | ||||||
|     queryset = ProposalBase.objects.filter(kind__section=section) |     queryset = ProposalBase.objects.filter(kind__section=section) | ||||||
|      | 
 | ||||||
|     if assigned: |     if assigned: | ||||||
|         assignments = ReviewAssignment.objects.filter(user=request.user).values_list("proposal__id") |         assignments = ReviewAssignment.objects.filter(user=request.user)\ | ||||||
|  |             .values_list("proposal__id") | ||||||
|         queryset = queryset.filter(id__in=assignments) |         queryset = queryset.filter(id__in=assignments) | ||||||
|      | 
 | ||||||
|     # passing reviewed in from reviews.urls and out to review_list for |     # passing reviewed in from reviews.urls and out to review_list for | ||||||
|     # appropriate template header rendering |     # appropriate template header rendering | ||||||
|     if reviewed == "all": |     if reviewed == "all": | ||||||
|  | @ -88,35 +87,36 @@ def review_section(request, section_slug, assigned=False, reviewed="all"): | ||||||
|     else: |     else: | ||||||
|         queryset = queryset.exclude(reviews__user=request.user).exclude(speaker=request.user) |         queryset = queryset.exclude(reviews__user=request.user).exclude(speaker=request.user) | ||||||
|         reviewed = "user_not_reviewed" |         reviewed = "user_not_reviewed" | ||||||
|      | 
 | ||||||
|     proposals = proposals_generator(request, queryset) |     proposals = proposals_generator(request, queryset) | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "proposals": proposals, |         "proposals": proposals, | ||||||
|         "section": section, |         "section": section, | ||||||
|         "reviewed": reviewed, |         "reviewed": reviewed, | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     return render(request, "reviews/review_list.html", ctx) |     return render(request, "reviews/review_list.html", ctx) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| @login_required | @login_required | ||||||
| def review_list(request, section_slug, user_pk): | def review_list(request, section_slug, user_pk): | ||||||
|      | 
 | ||||||
|     # if they're not a reviewer admin and they aren't the person whose |     # if they're not a reviewer admin and they aren't the person whose | ||||||
|     # review list is being asked for, don't let them in |     # review list is being asked for, don't let them in | ||||||
|     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): |     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): | ||||||
|         if not request.user.pk == user_pk: |         if not request.user.pk == user_pk: | ||||||
|             return access_not_permitted(request) |             return access_not_permitted(request) | ||||||
|      | 
 | ||||||
|     queryset = ProposalBase.objects.select_related("speaker__user", "result") |     queryset = ProposalBase.objects.select_related("speaker__user", "result") | ||||||
|     reviewed = LatestVote.objects.filter(user__pk=user_pk).values_list("proposal", flat=True) |     reviewed = LatestVote.objects.filter(user__pk=user_pk).values_list("proposal", flat=True) | ||||||
|     queryset = queryset.filter(pk__in=reviewed) |     queryset = queryset.filter(pk__in=reviewed) | ||||||
|     proposals = queryset.order_by("submitted") |     proposals = queryset.order_by("submitted") | ||||||
|      | 
 | ||||||
|     admin = request.user.has_perm("reviews.can_manage_%s" % section_slug) |     admin = request.user.has_perm("reviews.can_manage_%s" % section_slug) | ||||||
|      | 
 | ||||||
|     proposals = proposals_generator(request, proposals, user_pk=user_pk, check_speaker=not admin) |     proposals = proposals_generator(request, proposals, user_pk=user_pk, check_speaker=not admin) | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "proposals": proposals, |         "proposals": proposals, | ||||||
|     } |     } | ||||||
|  | @ -125,41 +125,41 @@ def review_list(request, section_slug, user_pk): | ||||||
| 
 | 
 | ||||||
| @login_required | @login_required | ||||||
| def review_admin(request, section_slug): | def review_admin(request, section_slug): | ||||||
|      | 
 | ||||||
|     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): |     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): | ||||||
|         return access_not_permitted(request) |         return access_not_permitted(request) | ||||||
|      | 
 | ||||||
|     def reviewers(): |     def reviewers(): | ||||||
|         already_seen = set() |         already_seen = set() | ||||||
|          | 
 | ||||||
|         for team in Team.objects.filter(permissions__codename="can_review_%s" % section_slug): |         for team in Team.objects.filter(permissions__codename="can_review_%s" % section_slug): | ||||||
|             for membership in team.memberships.filter(Q(state="member") | Q(state="manager")): |             for membership in team.memberships.filter(Q(state="member") | Q(state="manager")): | ||||||
|                 user = membership.user |                 user = membership.user | ||||||
|                 if user.pk in already_seen: |                 if user.pk in already_seen: | ||||||
|                     continue |                     continue | ||||||
|                 already_seen.add(user.pk) |                 already_seen.add(user.pk) | ||||||
|                  | 
 | ||||||
|                 user.comment_count = Review.objects.filter(user=user).count() |                 user.comment_count = Review.objects.filter(user=user).count() | ||||||
|                 user.total_votes = LatestVote.objects.filter(user=user).count() |                 user.total_votes = LatestVote.objects.filter(user=user).count() | ||||||
|                 user.plus_one = LatestVote.objects.filter( |                 user.plus_one = LatestVote.objects.filter( | ||||||
|                     user = user, |                     user=user, | ||||||
|                     vote = LatestVote.VOTES.PLUS_ONE |                     vote=LatestVote.VOTES.PLUS_ONE | ||||||
|                 ).count() |                 ).count() | ||||||
|                 user.plus_zero = LatestVote.objects.filter( |                 user.plus_zero = LatestVote.objects.filter( | ||||||
|                     user = user, |                     user=user, | ||||||
|                     vote = LatestVote.VOTES.PLUS_ZERO |                     vote=LatestVote.VOTES.PLUS_ZERO | ||||||
|                 ).count() |                 ).count() | ||||||
|                 user.minus_zero = LatestVote.objects.filter( |                 user.minus_zero = LatestVote.objects.filter( | ||||||
|                     user = user, |                     user=user, | ||||||
|                     vote = LatestVote.VOTES.MINUS_ZERO |                     vote=LatestVote.VOTES.MINUS_ZERO | ||||||
|                 ).count() |                 ).count() | ||||||
|                 user.minus_one = LatestVote.objects.filter( |                 user.minus_one = LatestVote.objects.filter( | ||||||
|                     user = user, |                     user=user, | ||||||
|                     vote = LatestVote.VOTES.MINUS_ONE |                     vote=LatestVote.VOTES.MINUS_ONE | ||||||
|                 ).count() |                 ).count() | ||||||
|                  | 
 | ||||||
|                 yield user |                 yield user | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "section_slug": section_slug, |         "section_slug": section_slug, | ||||||
|         "reviewers": reviewers(), |         "reviewers": reviewers(), | ||||||
|  | @ -169,50 +169,50 @@ def review_admin(request, section_slug): | ||||||
| 
 | 
 | ||||||
| @login_required | @login_required | ||||||
| def review_detail(request, pk): | def review_detail(request, pk): | ||||||
|      | 
 | ||||||
|     proposals = ProposalBase.objects.select_related("result").select_subclasses() |     proposals = ProposalBase.objects.select_related("result").select_subclasses() | ||||||
|     proposal = get_object_or_404(proposals, pk=pk) |     proposal = get_object_or_404(proposals, pk=pk) | ||||||
|      | 
 | ||||||
|     if not request.user.has_perm("reviews.can_review_%s" % proposal.kind.section.slug): |     if not request.user.has_perm("reviews.can_review_%s" % proposal.kind.section.slug): | ||||||
|         return access_not_permitted(request) |         return access_not_permitted(request) | ||||||
|      | 
 | ||||||
|     speakers = [s.user for s in proposal.speakers()] |     speakers = [s.user for s in proposal.speakers()] | ||||||
|      | 
 | ||||||
|     if not request.user.is_superuser and request.user in speakers: |     if not request.user.is_superuser and request.user in speakers: | ||||||
|         return access_not_permitted(request) |         return access_not_permitted(request) | ||||||
|      | 
 | ||||||
|     admin = request.user.is_staff |     admin = request.user.is_staff | ||||||
|      | 
 | ||||||
|     try: |     try: | ||||||
|         latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user) |         latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user) | ||||||
|     except LatestVote.DoesNotExist: |     except LatestVote.DoesNotExist: | ||||||
|         latest_vote = None |         latest_vote = None | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         if request.user in speakers: |         if request.user in speakers: | ||||||
|             return access_not_permitted(request) |             return access_not_permitted(request) | ||||||
|          | 
 | ||||||
|         if "vote_submit" in request.POST: |         if "vote_submit" in request.POST: | ||||||
|             review_form = ReviewForm(request.POST) |             review_form = ReviewForm(request.POST) | ||||||
|             if review_form.is_valid(): |             if review_form.is_valid(): | ||||||
|                  | 
 | ||||||
|                 review = review_form.save(commit=False) |                 review = review_form.save(commit=False) | ||||||
|                 review.user = request.user |                 review.user = request.user | ||||||
|                 review.proposal = proposal |                 review.proposal = proposal | ||||||
|                 review.save() |                 review.save() | ||||||
|                  | 
 | ||||||
|                 return redirect(request.path) |                 return redirect(request.path) | ||||||
|             else: |             else: | ||||||
|                 message_form = SpeakerCommentForm() |                 message_form = SpeakerCommentForm() | ||||||
|         elif "message_submit" in request.POST: |         elif "message_submit" in request.POST: | ||||||
|             message_form = SpeakerCommentForm(request.POST) |             message_form = SpeakerCommentForm(request.POST) | ||||||
|             if message_form.is_valid(): |             if message_form.is_valid(): | ||||||
|                  | 
 | ||||||
|                 message = message_form.save(commit=False) |                 message = message_form.save(commit=False) | ||||||
|                 message.user = request.user |                 message.user = request.user | ||||||
|                 message.proposal = proposal |                 message.proposal = proposal | ||||||
|                 message.save() |                 message.save() | ||||||
|                  | 
 | ||||||
|                 for speaker in speakers: |                 for speaker in speakers: | ||||||
|                     if speaker and speaker.email: |                     if speaker and speaker.email: | ||||||
|                         ctx = { |                         ctx = { | ||||||
|  | @ -222,9 +222,9 @@ def review_detail(request, pk): | ||||||
|                         } |                         } | ||||||
|                         send_email( |                         send_email( | ||||||
|                             [speaker.email], "proposal_new_message", |                             [speaker.email], "proposal_new_message", | ||||||
|                             context = ctx |                             context=ctx | ||||||
|                         ) |                         ) | ||||||
|                  | 
 | ||||||
|                 return redirect(request.path) |                 return redirect(request.path) | ||||||
|             else: |             else: | ||||||
|                 initial = {} |                 initial = {} | ||||||
|  | @ -237,7 +237,7 @@ def review_detail(request, pk): | ||||||
|         elif "result_submit" in request.POST: |         elif "result_submit" in request.POST: | ||||||
|             if admin: |             if admin: | ||||||
|                 result = request.POST["result_submit"] |                 result = request.POST["result_submit"] | ||||||
|                  | 
 | ||||||
|                 if result == "accept": |                 if result == "accept": | ||||||
|                     proposal.result.status = "accepted" |                     proposal.result.status = "accepted" | ||||||
|                     proposal.result.save() |                     proposal.result.save() | ||||||
|  | @ -250,7 +250,7 @@ def review_detail(request, pk): | ||||||
|                 elif result == "standby": |                 elif result == "standby": | ||||||
|                     proposal.result.status = "standby" |                     proposal.result.status = "standby" | ||||||
|                     proposal.result.save() |                     proposal.result.save() | ||||||
|              | 
 | ||||||
|             return redirect(request.path) |             return redirect(request.path) | ||||||
|     else: |     else: | ||||||
|         initial = {} |         initial = {} | ||||||
|  | @ -261,17 +261,17 @@ def review_detail(request, pk): | ||||||
|         else: |         else: | ||||||
|             review_form = ReviewForm(initial=initial) |             review_form = ReviewForm(initial=initial) | ||||||
|         message_form = SpeakerCommentForm() |         message_form = SpeakerCommentForm() | ||||||
|      | 
 | ||||||
|     proposal.comment_count = proposal.result.comment_count |     proposal.comment_count = proposal.result.comment_count | ||||||
|     proposal.total_votes = proposal.result.vote_count |     proposal.total_votes = proposal.result.vote_count | ||||||
|     proposal.plus_one = proposal.result.plus_one |     proposal.plus_one = proposal.result.plus_one | ||||||
|     proposal.plus_zero = proposal.result.plus_zero |     proposal.plus_zero = proposal.result.plus_zero | ||||||
|     proposal.minus_zero = proposal.result.minus_zero |     proposal.minus_zero = proposal.result.minus_zero | ||||||
|     proposal.minus_one = proposal.result.minus_one |     proposal.minus_one = proposal.result.minus_one | ||||||
|      | 
 | ||||||
|     reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at") |     reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at") | ||||||
|     messages = proposal.messages.order_by("submitted_at") |     messages = proposal.messages.order_by("submitted_at") | ||||||
|      | 
 | ||||||
|     return render(request, "reviews/review_detail.html", { |     return render(request, "reviews/review_detail.html", { | ||||||
|         "proposal": proposal, |         "proposal": proposal, | ||||||
|         "latest_vote": latest_vote, |         "latest_vote": latest_vote, | ||||||
|  | @ -287,53 +287,64 @@ def review_detail(request, pk): | ||||||
| def review_delete(request, pk): | def review_delete(request, pk): | ||||||
|     review = get_object_or_404(Review, pk=pk) |     review = get_object_or_404(Review, pk=pk) | ||||||
|     section_slug = review.section.slug |     section_slug = review.section.slug | ||||||
|      | 
 | ||||||
|     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): |     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): | ||||||
|         return access_not_permitted(request) |         return access_not_permitted(request) | ||||||
|      | 
 | ||||||
|     review = get_object_or_404(Review, pk=pk) |     review = get_object_or_404(Review, pk=pk) | ||||||
|     review.delete() |     review.delete() | ||||||
|      | 
 | ||||||
|     return redirect("review_detail", pk=review.proposal.pk) |     return redirect("review_detail", pk=review.proposal.pk) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @login_required | @login_required | ||||||
| def review_status(request, section_slug=None, key=None): | def review_status(request, section_slug=None, key=None): | ||||||
|      | 
 | ||||||
|     if not request.user.has_perm("reviews.can_review_%s" % section_slug): |     if not request.user.has_perm("reviews.can_review_%s" % section_slug): | ||||||
|         return access_not_permitted(request) |         return access_not_permitted(request) | ||||||
|      | 
 | ||||||
|     VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD |     VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "section_slug": section_slug, |         "section_slug": section_slug, | ||||||
|         "vote_threshold": VOTE_THRESHOLD, |         "vote_threshold": VOTE_THRESHOLD, | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses() |     queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses() | ||||||
|     if section_slug: |     if section_slug: | ||||||
|         queryset = queryset.filter(kind__section__slug=section_slug) |         queryset = queryset.filter(kind__section__slug=section_slug) | ||||||
|      | 
 | ||||||
|     proposals = { |     proposals = { | ||||||
|         # proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by the 'score' |         # proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by | ||||||
|         "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one=0).order_by("-result__score"), |         # the 'score' | ||||||
|         # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse sorted by the 'score' |         "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, | ||||||
|         "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0, result__plus_one=0).order_by("result__score"), |                                     result__minus_one=0).order_by("-result__score"), | ||||||
|         # proposals with at least VOTE_THRESHOLD reviews and neither a +1 or a -1, sorted by total votes (lowest first) |         # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse | ||||||
|         "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0, result__plus_one=0).order_by("result__vote_count"), |         # sorted by the 'score' | ||||||
|         # proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total votes (highest first) |         "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0, | ||||||
|         "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one__gt=0).order_by("-result__vote_count"), |                                     result__plus_one=0).order_by("result__score"), | ||||||
|  |         # proposals with at least VOTE_THRESHOLD reviews and neither a +1 or a -1, sorted by total | ||||||
|  |         # votes (lowest first) | ||||||
|  |         "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0, | ||||||
|  |                                        result__plus_one=0).order_by("result__vote_count"), | ||||||
|  |         # proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total | ||||||
|  |         # votes (highest first) | ||||||
|  |         "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, | ||||||
|  |                                          result__plus_one__gt=0, result__minus_one__gt=0) | ||||||
|  |         .order_by("-result__vote_count"), | ||||||
|         # proposals with fewer than VOTE_THRESHOLD reviews |         # proposals with fewer than VOTE_THRESHOLD reviews | ||||||
|         "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD).order_by("result__vote_count"), |         "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD) | ||||||
|  |         .order_by("result__vote_count"), | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     admin = request.user.has_perm("reviews.can_manage_%s" % section_slug) |     admin = request.user.has_perm("reviews.can_manage_%s" % section_slug) | ||||||
|      | 
 | ||||||
|     for status in proposals: |     for status in proposals: | ||||||
|         if key and key != status: |         if key and key != status: | ||||||
|             continue |             continue | ||||||
|         proposals[status] = list(proposals_generator(request, proposals[status], check_speaker=not admin)) |         proposals[status] = list(proposals_generator(request, proposals[status], | ||||||
|      |                                                      check_speaker=not admin)) | ||||||
|  | 
 | ||||||
|     if key: |     if key: | ||||||
|         ctx.update({ |         ctx.update({ | ||||||
|             "key": key, |             "key": key, | ||||||
|  | @ -341,7 +352,7 @@ def review_status(request, section_slug=None, key=None): | ||||||
|         }) |         }) | ||||||
|     else: |     else: | ||||||
|         ctx["proposals"] = proposals |         ctx["proposals"] = proposals | ||||||
|      | 
 | ||||||
|     return render(request, "reviews/review_stats.html", ctx) |     return render(request, "reviews/review_stats.html", ctx) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -361,14 +372,13 @@ def review_assignments(request): | ||||||
| @login_required | @login_required | ||||||
| @require_POST | @require_POST | ||||||
| def review_assignment_opt_out(request, pk): | def review_assignment_opt_out(request, pk): | ||||||
|     review_assignment = get_object_or_404(ReviewAssignment, |     review_assignment = get_object_or_404( | ||||||
|         pk=pk, |         ReviewAssignment, pk=pk, user=request.user) | ||||||
|         user=request.user |  | ||||||
|     ) |  | ||||||
|     if not review_assignment.opted_out: |     if not review_assignment.opted_out: | ||||||
|         review_assignment.opted_out = True |         review_assignment.opted_out = True | ||||||
|         review_assignment.save() |         review_assignment.save() | ||||||
|         ReviewAssignment.create_assignments(review_assignment.proposal, origin=ReviewAssignment.AUTO_ASSIGNED_LATER) |         ReviewAssignment.create_assignments( | ||||||
|  |             review_assignment.proposal, origin=ReviewAssignment.AUTO_ASSIGNED_LATER) | ||||||
|     return redirect("review_assignments") |     return redirect("review_assignments") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -387,7 +397,7 @@ def review_bulk_accept(request, section_slug): | ||||||
|             return redirect("review_section", section_slug=section_slug) |             return redirect("review_section", section_slug=section_slug) | ||||||
|     else: |     else: | ||||||
|         form = BulkPresentationForm() |         form = BulkPresentationForm() | ||||||
|      | 
 | ||||||
|     return render(request, "reviews/review_bulk_accept.html", { |     return render(request, "reviews/review_bulk_accept.html", { | ||||||
|         "form": form, |         "form": form, | ||||||
|     }) |     }) | ||||||
|  | @ -397,10 +407,12 @@ def review_bulk_accept(request, section_slug): | ||||||
| def result_notification(request, section_slug, status): | def result_notification(request, section_slug, status): | ||||||
|     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): |     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): | ||||||
|         return access_not_permitted(request) |         return access_not_permitted(request) | ||||||
|      | 
 | ||||||
|     proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, result__status=status).select_related("speaker__user", "result").select_subclasses() |     proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, | ||||||
|  |                                             result__status=status)\ | ||||||
|  |         .select_related("speaker__user", "result").select_subclasses() | ||||||
|     notification_templates = NotificationTemplate.objects.all() |     notification_templates = NotificationTemplate.objects.all() | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "section_slug": section_slug, |         "section_slug": section_slug, | ||||||
|         "status": status, |         "status": status, | ||||||
|  | @ -414,10 +426,10 @@ def result_notification(request, section_slug, status): | ||||||
| def result_notification_prepare(request, section_slug, status): | def result_notification_prepare(request, section_slug, status): | ||||||
|     if request.method != "POST": |     if request.method != "POST": | ||||||
|         return HttpResponseNotAllowed(["POST"]) |         return HttpResponseNotAllowed(["POST"]) | ||||||
|      | 
 | ||||||
|     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): |     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): | ||||||
|         return access_not_permitted(request) |         return access_not_permitted(request) | ||||||
|      | 
 | ||||||
|     proposal_pks = [] |     proposal_pks = [] | ||||||
|     try: |     try: | ||||||
|         for pk in request.POST.getlist("_selected_action"): |         for pk in request.POST.getlist("_selected_action"): | ||||||
|  | @ -431,13 +443,13 @@ def result_notification_prepare(request, section_slug, status): | ||||||
|     proposals = proposals.filter(pk__in=proposal_pks) |     proposals = proposals.filter(pk__in=proposal_pks) | ||||||
|     proposals = proposals.select_related("speaker__user", "result") |     proposals = proposals.select_related("speaker__user", "result") | ||||||
|     proposals = proposals.select_subclasses() |     proposals = proposals.select_subclasses() | ||||||
|      | 
 | ||||||
|     notification_template_pk = request.POST.get("notification_template", "") |     notification_template_pk = request.POST.get("notification_template", "") | ||||||
|     if notification_template_pk: |     if notification_template_pk: | ||||||
|         notification_template = NotificationTemplate.objects.get(pk=notification_template_pk) |         notification_template = NotificationTemplate.objects.get(pk=notification_template_pk) | ||||||
|     else: |     else: | ||||||
|         notification_template = None |         notification_template = None | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "section_slug": section_slug, |         "section_slug": section_slug, | ||||||
|         "status": status, |         "status": status, | ||||||
|  | @ -452,18 +464,18 @@ def result_notification_prepare(request, section_slug, status): | ||||||
| def result_notification_send(request, section_slug, status): | def result_notification_send(request, section_slug, status): | ||||||
|     if request.method != "POST": |     if request.method != "POST": | ||||||
|         return HttpResponseNotAllowed(["POST"]) |         return HttpResponseNotAllowed(["POST"]) | ||||||
|      | 
 | ||||||
|     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): |     if not request.user.has_perm("reviews.can_manage_%s" % section_slug): | ||||||
|         return access_not_permitted(request) |         return access_not_permitted(request) | ||||||
|      | 
 | ||||||
|     if not all([k in request.POST for k in ["proposal_pks", "from_address", "subject", "body"]]): |     if not all([k in request.POST for k in ["proposal_pks", "from_address", "subject", "body"]]): | ||||||
|         return HttpResponseBadRequest() |         return HttpResponseBadRequest() | ||||||
|      | 
 | ||||||
|     try: |     try: | ||||||
|         proposal_pks = [int(pk) for pk in request.POST["proposal_pks"].split(",")] |         proposal_pks = [int(pk) for pk in request.POST["proposal_pks"].split(",")] | ||||||
|     except ValueError: |     except ValueError: | ||||||
|         return HttpResponseBadRequest() |         return HttpResponseBadRequest() | ||||||
|      | 
 | ||||||
|     proposals = ProposalBase.objects.filter( |     proposals = ProposalBase.objects.filter( | ||||||
|         kind__section__slug=section_slug, |         kind__section__slug=section_slug, | ||||||
|         result__status=status, |         result__status=status, | ||||||
|  | @ -471,15 +483,15 @@ def result_notification_send(request, section_slug, status): | ||||||
|     proposals = proposals.filter(pk__in=proposal_pks) |     proposals = proposals.filter(pk__in=proposal_pks) | ||||||
|     proposals = proposals.select_related("speaker__user", "result") |     proposals = proposals.select_related("speaker__user", "result") | ||||||
|     proposals = proposals.select_subclasses() |     proposals = proposals.select_subclasses() | ||||||
|      | 
 | ||||||
|     notification_template_pk = request.POST.get("notification_template", "") |     notification_template_pk = request.POST.get("notification_template", "") | ||||||
|     if notification_template_pk: |     if notification_template_pk: | ||||||
|         notification_template = NotificationTemplate.objects.get(pk=notification_template_pk) |         notification_template = NotificationTemplate.objects.get(pk=notification_template_pk) | ||||||
|     else: |     else: | ||||||
|         notification_template = None |         notification_template = None | ||||||
|      | 
 | ||||||
|     emails = [] |     emails = [] | ||||||
|      | 
 | ||||||
|     for proposal in proposals: |     for proposal in proposals: | ||||||
|         rn = ResultNotification() |         rn = ResultNotification() | ||||||
|         rn.proposal = proposal |         rn.proposal = proposal | ||||||
|  | @ -494,7 +506,7 @@ def result_notification_send(request, section_slug, status): | ||||||
|         ) |         ) | ||||||
|         rn.save() |         rn.save() | ||||||
|         emails.append(rn.email_args) |         emails.append(rn.email_args) | ||||||
|      | 
 | ||||||
|     send_mass_mail(emails) |     send_mass_mail(emails) | ||||||
|      | 
 | ||||||
|     return redirect("result_notification", section_slug=section_slug, status=status) |     return redirect("result_notification", section_slug=section_slug, status=status) | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ from symposion.schedule.models import Presentation | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SlotEditForm(forms.Form): | class SlotEditForm(forms.Form): | ||||||
|      | 
 | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         self.slot = kwargs.pop("slot") |         self.slot = kwargs.pop("slot") | ||||||
|         super(SlotEditForm, self).__init__(*args, **kwargs) |         super(SlotEditForm, self).__init__(*args, **kwargs) | ||||||
|  | @ -16,7 +16,7 @@ class SlotEditForm(forms.Form): | ||||||
|             self.fields["presentation"] = self.build_presentation_field() |             self.fields["presentation"] = self.build_presentation_field() | ||||||
|         else: |         else: | ||||||
|             self.fields["content_override"] = self.build_content_override_field() |             self.fields["content_override"] = self.build_content_override_field() | ||||||
|      | 
 | ||||||
|     def build_presentation_field(self): |     def build_presentation_field(self): | ||||||
|         kwargs = {} |         kwargs = {} | ||||||
|         queryset = Presentation.objects.all() |         queryset = Presentation.objects.all() | ||||||
|  | @ -31,7 +31,7 @@ class SlotEditForm(forms.Form): | ||||||
|             kwargs["required"] = True |             kwargs["required"] = True | ||||||
|         kwargs["queryset"] = queryset |         kwargs["queryset"] = queryset | ||||||
|         return forms.ModelChoiceField(**kwargs) |         return forms.ModelChoiceField(**kwargs) | ||||||
|      | 
 | ||||||
|     def build_content_override_field(self): |     def build_content_override_field(self): | ||||||
|         kwargs = { |         kwargs = { | ||||||
|             "label": "Content", |             "label": "Content", | ||||||
|  |  | ||||||
|  | @ -2,44 +2,43 @@ from django.core.exceptions import ObjectDoesNotExist | ||||||
| from django.db import models | from django.db import models | ||||||
| 
 | 
 | ||||||
| from markitup.fields import MarkupField | from markitup.fields import MarkupField | ||||||
| from model_utils.managers import InheritanceManager |  | ||||||
| 
 | 
 | ||||||
| from symposion.proposals.models import ProposalBase | from symposion.proposals.models import ProposalBase | ||||||
| from symposion.conference.models import Section | from symposion.conference.models import Section | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Schedule(models.Model): | class Schedule(models.Model): | ||||||
|      | 
 | ||||||
|     section = models.OneToOneField(Section) |     section = models.OneToOneField(Section) | ||||||
|     published = models.BooleanField(default=True) |     published = models.BooleanField(default=True) | ||||||
|     hidden = models.BooleanField("Hide schedule from overall conference view", default=False) |     hidden = models.BooleanField("Hide schedule from overall conference view", default=False) | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return "%s Schedule" % self.section |         return "%s Schedule" % self.section | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         ordering = ["section"] |         ordering = ["section"] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Day(models.Model): | class Day(models.Model): | ||||||
|      | 
 | ||||||
|     schedule = models.ForeignKey(Schedule) |     schedule = models.ForeignKey(Schedule) | ||||||
|     date = models.DateField() |     date = models.DateField() | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return "%s" % self.date |         return "%s" % self.date | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         unique_together = [("schedule", "date")] |         unique_together = [("schedule", "date")] | ||||||
|         ordering = ["date"] |         ordering = ["date"] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Room(models.Model): | class Room(models.Model): | ||||||
|      | 
 | ||||||
|     schedule = models.ForeignKey(Schedule) |     schedule = models.ForeignKey(Schedule) | ||||||
|     name = models.CharField(max_length=65) |     name = models.CharField(max_length=65) | ||||||
|     order = models.PositiveIntegerField() |     order = models.PositiveIntegerField() | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.name |         return self.name | ||||||
| 
 | 
 | ||||||
|  | @ -49,22 +48,22 @@ class SlotKind(models.Model): | ||||||
|     A slot kind represents what kind a slot is. For example, a slot can be a |     A slot kind represents what kind a slot is. For example, a slot can be a | ||||||
|     break, lunch, or X-minute talk. |     break, lunch, or X-minute talk. | ||||||
|     """ |     """ | ||||||
|      | 
 | ||||||
|     schedule = models.ForeignKey(Schedule) |     schedule = models.ForeignKey(Schedule) | ||||||
|     label = models.CharField(max_length=50) |     label = models.CharField(max_length=50) | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.label |         return self.label | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Slot(models.Model): | class Slot(models.Model): | ||||||
|      | 
 | ||||||
|     day = models.ForeignKey(Day) |     day = models.ForeignKey(Day) | ||||||
|     kind = models.ForeignKey(SlotKind) |     kind = models.ForeignKey(SlotKind) | ||||||
|     start = models.TimeField() |     start = models.TimeField() | ||||||
|     end = models.TimeField() |     end = models.TimeField() | ||||||
|     content_override = MarkupField(blank=True) |     content_override = MarkupField(blank=True) | ||||||
|      | 
 | ||||||
|     def assign(self, content): |     def assign(self, content): | ||||||
|         """ |         """ | ||||||
|         Assign the given content to this slot and if a previous slot content |         Assign the given content to this slot and if a previous slot content | ||||||
|  | @ -73,7 +72,7 @@ class Slot(models.Model): | ||||||
|         self.unassign() |         self.unassign() | ||||||
|         content.slot = self |         content.slot = self | ||||||
|         content.save() |         content.save() | ||||||
|      | 
 | ||||||
|     def unassign(self): |     def unassign(self): | ||||||
|         """ |         """ | ||||||
|         Unassign the associated content with this slot. |         Unassign the associated content with this slot. | ||||||
|  | @ -81,7 +80,7 @@ class Slot(models.Model): | ||||||
|         if self.content and self.content.slot_id: |         if self.content and self.content.slot_id: | ||||||
|             self.content.slot = None |             self.content.slot = None | ||||||
|             self.content.save() |             self.content.save() | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def content(self): |     def content(self): | ||||||
|         """ |         """ | ||||||
|  | @ -92,14 +91,14 @@ class Slot(models.Model): | ||||||
|             return self.content_ptr |             return self.content_ptr | ||||||
|         except ObjectDoesNotExist: |         except ObjectDoesNotExist: | ||||||
|             return None |             return None | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def rooms(self): |     def rooms(self): | ||||||
|         return Room.objects.filter(pk__in=self.slotroom_set.values("room")) |         return Room.objects.filter(pk__in=self.slotroom_set.values("room")) | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return "%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end) |         return "%s %s (%s - %s)" % (self.day, self.kind, self.start, self.end) | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         ordering = ["day", "start", "end"] |         ordering = ["day", "start", "end"] | ||||||
| 
 | 
 | ||||||
|  | @ -108,48 +107,49 @@ class SlotRoom(models.Model): | ||||||
|     """ |     """ | ||||||
|     Links a slot with a room. |     Links a slot with a room. | ||||||
|     """ |     """ | ||||||
|      | 
 | ||||||
|     slot = models.ForeignKey(Slot) |     slot = models.ForeignKey(Slot) | ||||||
|     room = models.ForeignKey(Room) |     room = models.ForeignKey(Room) | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return "%s %s" % (self.room, self.slot) |         return "%s %s" % (self.room, self.slot) | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         unique_together = [("slot", "room")] |         unique_together = [("slot", "room")] | ||||||
|         ordering = ["slot", "room__order"] |         ordering = ["slot", "room__order"] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Presentation(models.Model): | class Presentation(models.Model): | ||||||
|      | 
 | ||||||
|     slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr") |     slot = models.OneToOneField(Slot, null=True, blank=True, related_name="content_ptr") | ||||||
|     title = models.CharField(max_length=100) |     title = models.CharField(max_length=100) | ||||||
|     description = MarkupField() |     description = MarkupField() | ||||||
|     abstract = MarkupField() |     abstract = MarkupField() | ||||||
|     speaker = models.ForeignKey("speakers.Speaker", related_name="presentations") |     speaker = models.ForeignKey("speakers.Speaker", related_name="presentations") | ||||||
|     additional_speakers = models.ManyToManyField("speakers.Speaker", related_name="copresentations", blank=True) |     additional_speakers = models.ManyToManyField("speakers.Speaker", related_name="copresentations", | ||||||
|  |                                                  blank=True) | ||||||
|     cancelled = models.BooleanField(default=False) |     cancelled = models.BooleanField(default=False) | ||||||
|     proposal_base = models.OneToOneField(ProposalBase, related_name="presentation") |     proposal_base = models.OneToOneField(ProposalBase, related_name="presentation") | ||||||
|     section = models.ForeignKey(Section, related_name="presentations") |     section = models.ForeignKey(Section, related_name="presentations") | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def number(self): |     def number(self): | ||||||
|         return self.proposal.number |         return self.proposal.number | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def proposal(self): |     def proposal(self): | ||||||
|         if self.proposal_base_id is None: |         if self.proposal_base_id is None: | ||||||
|             return None |             return None | ||||||
|         return ProposalBase.objects.get_subclass(pk=self.proposal_base_id) |         return ProposalBase.objects.get_subclass(pk=self.proposal_base_id) | ||||||
|      | 
 | ||||||
|     def speakers(self): |     def speakers(self): | ||||||
|         yield self.speaker |         yield self.speaker | ||||||
|         for speaker in self.additional_speakers.all(): |         for speaker in self.additional_speakers.all(): | ||||||
|             if speaker.user: |             if speaker.user: | ||||||
|                 yield speaker |                 yield speaker | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return "#%s %s (%s)" % (self.number, self.title, self.speaker) |         return "#%s %s (%s)" % (self.number, self.title, self.speaker) | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         ordering = ["slot"] |         ordering = ["slot"] | ||||||
|  |  | ||||||
|  | @ -1,5 +1,4 @@ | ||||||
| import itertools | import itertools | ||||||
| import operator |  | ||||||
| 
 | 
 | ||||||
| from django.db.models import Count, Min | from django.db.models import Count, Min | ||||||
| 
 | 
 | ||||||
|  | @ -7,22 +6,23 @@ from symposion.schedule.models import Room, Slot, SlotRoom | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TimeTable(object): | class TimeTable(object): | ||||||
|      | 
 | ||||||
|     def __init__(self, day): |     def __init__(self, day): | ||||||
|         self.day = day |         self.day = day | ||||||
|      | 
 | ||||||
|     def slots_qs(self): |     def slots_qs(self): | ||||||
|         qs = Slot.objects.all() |         qs = Slot.objects.all() | ||||||
|         qs = qs.filter(day=self.day) |         qs = qs.filter(day=self.day) | ||||||
|         return qs |         return qs | ||||||
|      | 
 | ||||||
|     def rooms(self): |     def rooms(self): | ||||||
|         qs = Room.objects.all() |         qs = Room.objects.all() | ||||||
|         qs = qs.filter(schedule=self.day.schedule) |         qs = qs.filter(schedule=self.day.schedule) | ||||||
|         qs = qs.filter(pk__in=SlotRoom.objects.filter(slot__in=self.slots_qs().values("pk")).values("room")) |         qs = qs.filter( | ||||||
|  |             pk__in=SlotRoom.objects.filter(slot__in=self.slots_qs().values("pk")).values("room")) | ||||||
|         qs = qs.order_by("order") |         qs = qs.order_by("order") | ||||||
|         return qs |         return qs | ||||||
|      | 
 | ||||||
|     def __iter__(self): |     def __iter__(self): | ||||||
|         times = sorted(set(itertools.chain(*self.slots_qs().values_list("start", "end")))) |         times = sorted(set(itertools.chain(*self.slots_qs().values_list("start", "end")))) | ||||||
|         slots = Slot.objects.filter(pk__in=self.slots_qs().values("pk")) |         slots = Slot.objects.filter(pk__in=self.slots_qs().values("pk")) | ||||||
|  | @ -38,7 +38,7 @@ class TimeTable(object): | ||||||
|                     row["slots"].append(slot) |                     row["slots"].append(slot) | ||||||
|             if row["slots"] or next_time is None: |             if row["slots"] or next_time is None: | ||||||
|                 yield row |                 yield row | ||||||
|      | 
 | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def rowspan(times, start, end): |     def rowspan(times, start, end): | ||||||
|         return times.index(end) - times.index(start) |         return times.index(end) - times.index(start) | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # flake8: noqa | ||||||
| from django.conf.urls.defaults import url, patterns | from django.conf.urls.defaults import url, patterns | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| from django.core.exceptions import ObjectDoesNotExist |  | ||||||
| from django.http import Http404, HttpResponse | from django.http import Http404, HttpResponse | ||||||
| from django.shortcuts import render, get_object_or_404, redirect | from django.shortcuts import render, get_object_or_404, redirect | ||||||
| from django.template import loader, Context | from django.template import loader, Context | ||||||
|  | @ -12,7 +11,7 @@ from symposion.schedule.timetable import TimeTable | ||||||
| 
 | 
 | ||||||
| def fetch_schedule(slug): | def fetch_schedule(slug): | ||||||
|     qs = Schedule.objects.all() |     qs = Schedule.objects.all() | ||||||
|      | 
 | ||||||
|     if slug is None: |     if slug is None: | ||||||
|         if qs.count() > 1: |         if qs.count() > 1: | ||||||
|             raise Http404() |             raise Http404() | ||||||
|  | @ -21,14 +20,14 @@ def fetch_schedule(slug): | ||||||
|             raise Http404() |             raise Http404() | ||||||
|     else: |     else: | ||||||
|         schedule = get_object_or_404(qs, section__slug=slug) |         schedule = get_object_or_404(qs, section__slug=slug) | ||||||
|      | 
 | ||||||
|     return schedule |     return schedule | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def schedule_conference(request): | def schedule_conference(request): | ||||||
|      | 
 | ||||||
|     schedules = Schedule.objects.filter(published=True, hidden=False) |     schedules = Schedule.objects.filter(published=True, hidden=False) | ||||||
|      | 
 | ||||||
|     sections = [] |     sections = [] | ||||||
|     for schedule in schedules: |     for schedule in schedules: | ||||||
|         days_qs = Day.objects.filter(schedule=schedule) |         days_qs = Day.objects.filter(schedule=schedule) | ||||||
|  | @ -37,7 +36,7 @@ def schedule_conference(request): | ||||||
|             "schedule": schedule, |             "schedule": schedule, | ||||||
|             "days": days, |             "days": days, | ||||||
|         }) |         }) | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "sections": sections, |         "sections": sections, | ||||||
|     } |     } | ||||||
|  | @ -45,14 +44,14 @@ def schedule_conference(request): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def schedule_detail(request, slug=None): | def schedule_detail(request, slug=None): | ||||||
|      | 
 | ||||||
|     schedule = fetch_schedule(slug) |     schedule = fetch_schedule(slug) | ||||||
|     if not schedule.published and not request.user.is_staff: |     if not schedule.published and not request.user.is_staff: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     days_qs = Day.objects.filter(schedule=schedule) |     days_qs = Day.objects.filter(schedule=schedule) | ||||||
|     days = [TimeTable(day) for day in days_qs] |     days = [TimeTable(day) for day in days_qs] | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "schedule": schedule, |         "schedule": schedule, | ||||||
|         "days": days, |         "days": days, | ||||||
|  | @ -62,10 +61,10 @@ def schedule_detail(request, slug=None): | ||||||
| 
 | 
 | ||||||
| def schedule_list(request, slug=None): | def schedule_list(request, slug=None): | ||||||
|     schedule = fetch_schedule(slug) |     schedule = fetch_schedule(slug) | ||||||
|      | 
 | ||||||
|     presentations = Presentation.objects.filter(section=schedule.section) |     presentations = Presentation.objects.filter(section=schedule.section) | ||||||
|     presentations = presentations.exclude(cancelled=True) |     presentations = presentations.exclude(cancelled=True) | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "schedule": schedule, |         "schedule": schedule, | ||||||
|         "presentations": presentations, |         "presentations": presentations, | ||||||
|  | @ -75,32 +74,32 @@ def schedule_list(request, slug=None): | ||||||
| 
 | 
 | ||||||
| def schedule_list_csv(request, slug=None): | def schedule_list_csv(request, slug=None): | ||||||
|     schedule = fetch_schedule(slug) |     schedule = fetch_schedule(slug) | ||||||
|      | 
 | ||||||
|     presentations = Presentation.objects.filter(section=schedule.section) |     presentations = Presentation.objects.filter(section=schedule.section) | ||||||
|     presentations = presentations.exclude(cancelled=True).order_by("id") |     presentations = presentations.exclude(cancelled=True).order_by("id") | ||||||
|      | 
 | ||||||
|     response = HttpResponse(mimetype="text/csv") |     response = HttpResponse(mimetype="text/csv") | ||||||
|     if slug: |     if slug: | ||||||
|         file_slug = slug |         file_slug = slug | ||||||
|     else: |     else: | ||||||
|         file_slug = "presentations" |         file_slug = "presentations" | ||||||
|     response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug |     response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug | ||||||
|      | 
 | ||||||
|     response.write(loader.get_template("schedule/schedule_list.csv").render(Context({ |     response.write(loader.get_template("schedule/schedule_list.csv").render(Context({ | ||||||
|         "presentations": presentations, |         "presentations": presentations, | ||||||
|          | 
 | ||||||
|     }))) |     }))) | ||||||
|     return response |     return response | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @login_required | @login_required | ||||||
| def schedule_edit(request, slug=None): | def schedule_edit(request, slug=None): | ||||||
|      | 
 | ||||||
|     if not request.user.is_staff: |     if not request.user.is_staff: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     schedule = fetch_schedule(slug) |     schedule = fetch_schedule(slug) | ||||||
|      | 
 | ||||||
|     days_qs = Day.objects.filter(schedule=schedule) |     days_qs = Day.objects.filter(schedule=schedule) | ||||||
|     days = [TimeTable(day) for day in days_qs] |     days = [TimeTable(day) for day in days_qs] | ||||||
|     ctx = { |     ctx = { | ||||||
|  | @ -112,12 +111,12 @@ def schedule_edit(request, slug=None): | ||||||
| 
 | 
 | ||||||
| @login_required | @login_required | ||||||
| def schedule_slot_edit(request, slug, slot_pk): | def schedule_slot_edit(request, slug, slot_pk): | ||||||
|      | 
 | ||||||
|     if not request.user.is_staff: |     if not request.user.is_staff: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk) |     slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk) | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         form = SlotEditForm(request.POST, slot=slot) |         form = SlotEditForm(request.POST, slot=slot) | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|  | @ -145,13 +144,13 @@ def schedule_slot_edit(request, slug, slot_pk): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def schedule_presentation_detail(request, pk): | def schedule_presentation_detail(request, pk): | ||||||
|      | 
 | ||||||
|     presentation = get_object_or_404(Presentation, pk=pk) |     presentation = get_object_or_404(Presentation, pk=pk) | ||||||
|     if presentation.slot: |     if presentation.slot: | ||||||
|         schedule = presentation.slot.day.schedule |         schedule = presentation.slot.day.schedule | ||||||
|     else: |     else: | ||||||
|         schedule = None |         schedule = None | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "presentation": presentation, |         "presentation": presentation, | ||||||
|         "schedule": schedule, |         "schedule": schedule, | ||||||
|  |  | ||||||
|  | @ -4,6 +4,5 @@ from symposion.speakers.models import Speaker | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| admin.site.register(Speaker, | admin.site.register(Speaker, | ||||||
|     list_display = ["name", "email", "created"], |                     list_display=["name", "email", "created"], | ||||||
|     search_fields = ["name"], |                     search_fields=["name"]) | ||||||
| ) |  | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ def speakers(): | ||||||
|     guido = User.objects.create_user("guido", "guido@python.org", "pythonisawesome") |     guido = User.objects.create_user("guido", "guido@python.org", "pythonisawesome") | ||||||
|     matz = User.objects.create_user("matz", "matz@ruby.org", "pythonsucks") |     matz = User.objects.create_user("matz", "matz@ruby.org", "pythonsucks") | ||||||
|     larry = User.objects.create_user("larryw", "larry@perl.org", "linenoisehere") |     larry = User.objects.create_user("larryw", "larry@perl.org", "linenoisehere") | ||||||
|      | 
 | ||||||
|     Speaker.objects.create( |     Speaker.objects.create( | ||||||
|         user=guido, |         user=guido, | ||||||
|         name="Guido van Rossum", |         name="Guido van Rossum", | ||||||
|  | @ -19,8 +19,8 @@ def speakers(): | ||||||
|     Speaker.objects.create( |     Speaker.objects.create( | ||||||
|         user=matz, |         user=matz, | ||||||
|         name="Yukihiro Matsumoto", |         name="Yukihiro Matsumoto", | ||||||
|         biography="I wrote Ruby, and named it after the rare gem Ruby, a pun " |         biography=("I wrote Ruby, and named it after the rare gem Ruby, a pun " | ||||||
|             "on Perl/pearl.", |                    "on Perl/pearl."), | ||||||
|     ) |     ) | ||||||
|     Speaker.objects.create( |     Speaker.objects.create( | ||||||
|         user=larry, |         user=larry, | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ from symposion.speakers.models import Speaker | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SpeakerForm(forms.ModelForm): | class SpeakerForm(forms.ModelForm): | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = Speaker |         model = Speaker | ||||||
|         fields = [ |         fields = [ | ||||||
|  |  | ||||||
|  | @ -1,17 +1,17 @@ | ||||||
| import csv | import csv | ||||||
| import os | import os | ||||||
| 
 | 
 | ||||||
| from django.core.management.base import BaseCommand, CommandError | from django.core.management.base import BaseCommand | ||||||
| 
 | 
 | ||||||
| from symposion.speakers.models import Speaker | from symposion.speakers.models import Speaker | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Command(BaseCommand): | class Command(BaseCommand): | ||||||
|      | 
 | ||||||
|     def handle(self, *args, **options): |     def handle(self, *args, **options): | ||||||
|         csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb")) |         csv_file = csv.writer(open(os.path.join(os.getcwd(), "speakers.csv"), "wb")) | ||||||
|         csv_file.writerow(["Name", "Bio"]) |         csv_file.writerow(["Name", "Bio"]) | ||||||
|          | 
 | ||||||
|         for speaker in Speaker.objects.all(): |         for speaker in Speaker.objects.all(): | ||||||
|             csv_file.writerow([ |             csv_file.writerow([ | ||||||
|                 speaker.name.encode("utf-8"), |                 speaker.name.encode("utf-8"), | ||||||
|  |  | ||||||
|  | @ -9,44 +9,47 @@ from markitup.fields import MarkupField | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Speaker(models.Model): | class Speaker(models.Model): | ||||||
|      | 
 | ||||||
|     SESSION_COUNT_CHOICES = [ |     SESSION_COUNT_CHOICES = [ | ||||||
|         (1, "One"), |         (1, "One"), | ||||||
|         (2, "Two") |         (2, "Two") | ||||||
|     ] |     ] | ||||||
|      | 
 | ||||||
|     user = models.OneToOneField(User, null=True, related_name="speaker_profile") |     user = models.OneToOneField(User, null=True, related_name="speaker_profile") | ||||||
|     name = models.CharField(max_length=100, help_text="As you would like it to appear in the conference program.") |     name = models.CharField(max_length=100, help_text=("As you would like it to appear in the " | ||||||
|     biography = MarkupField(blank=True, help_text="A little bit about you. Edit using <a href='http://warpedvisions.org/projects/markdown-cheat-sheet/' target='_blank'>Markdown</a>.") |                                                        "conference program.")) | ||||||
|  |     biography = MarkupField(blank=True, help_text=("A little bit about you.  Edit using " | ||||||
|  |                                                    "<a href='http://warpedvisions.org/projects/" | ||||||
|  |                                                    "markdown-cheat-sheet/target='_blank'>" | ||||||
|  |                                                    "Markdown</a>.")) | ||||||
|     photo = models.ImageField(upload_to="speaker_photos", blank=True) |     photo = models.ImageField(upload_to="speaker_photos", blank=True) | ||||||
|     annotation = models.TextField()  # staff only |     annotation = models.TextField()  # staff only | ||||||
|     invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True) |     invite_email = models.CharField(max_length=200, unique=True, null=True, db_index=True) | ||||||
|     invite_token = models.CharField(max_length=40, db_index=True) |     invite_token = models.CharField(max_length=40, db_index=True) | ||||||
|     created = models.DateTimeField( |     created = models.DateTimeField( | ||||||
|         default = datetime.datetime.now, |         default=datetime.datetime.now, | ||||||
|         editable = False |         editable=False | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         ordering = ['name'] |         ordering = ['name'] | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         if self.user: |         if self.user: | ||||||
|             return self.name |             return self.name | ||||||
|         else: |         else: | ||||||
|             return "?" |             return "?" | ||||||
| 
 | 
 | ||||||
|      |  | ||||||
|     def get_absolute_url(self): |     def get_absolute_url(self): | ||||||
|         return reverse("speaker_edit") |         return reverse("speaker_edit") | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def email(self): |     def email(self): | ||||||
|         if self.user is not None: |         if self.user is not None: | ||||||
|             return self.user.email |             return self.user.email | ||||||
|         else: |         else: | ||||||
|             return self.invite_email |             return self.invite_email | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def all_presentations(self): |     def all_presentations(self): | ||||||
|         presentations = [] |         presentations = [] | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # flake8: noqa | ||||||
| from django.conf.urls.defaults import * | from django.conf.urls.defaults import * | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -17,7 +17,7 @@ def speaker_create(request): | ||||||
|         return redirect(request.user.speaker_profile) |         return redirect(request.user.speaker_profile) | ||||||
|     except ObjectDoesNotExist: |     except ObjectDoesNotExist: | ||||||
|         pass |         pass | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         try: |         try: | ||||||
|             speaker = Speaker.objects.get(invite_email=request.user.email) |             speaker = Speaker.objects.get(invite_email=request.user.email) | ||||||
|  | @ -26,7 +26,7 @@ def speaker_create(request): | ||||||
|             speaker = None |             speaker = None | ||||||
|             found = False |             found = False | ||||||
|         form = SpeakerForm(request.POST, request.FILES, instance=speaker) |         form = SpeakerForm(request.POST, request.FILES, instance=speaker) | ||||||
|          | 
 | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|             speaker = form.save(commit=False) |             speaker = form.save(commit=False) | ||||||
|             speaker.user = request.user |             speaker.user = request.user | ||||||
|  | @ -37,7 +37,7 @@ def speaker_create(request): | ||||||
|             return redirect("dashboard") |             return redirect("dashboard") | ||||||
|     else: |     else: | ||||||
|         form = SpeakerForm(initial={"name": request.user.get_full_name()}) |         form = SpeakerForm(initial={"name": request.user.get_full_name()}) | ||||||
|      | 
 | ||||||
|     return render(request, "speakers/speaker_create.html", { |     return render(request, "speakers/speaker_create.html", { | ||||||
|         "form": form, |         "form": form, | ||||||
|     }) |     }) | ||||||
|  | @ -48,15 +48,15 @@ def speaker_create_staff(request, pk): | ||||||
|     user = get_object_or_404(User, pk=pk) |     user = get_object_or_404(User, pk=pk) | ||||||
|     if not request.user.is_staff: |     if not request.user.is_staff: | ||||||
|         raise Http404 |         raise Http404 | ||||||
|      | 
 | ||||||
|     try: |     try: | ||||||
|         return redirect(user.speaker_profile) |         return redirect(user.speaker_profile) | ||||||
|     except ObjectDoesNotExist: |     except ObjectDoesNotExist: | ||||||
|         pass |         pass | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         form = SpeakerForm(request.POST, request.FILES) |         form = SpeakerForm(request.POST, request.FILES) | ||||||
|          | 
 | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|             speaker = form.save(commit=False) |             speaker = form.save(commit=False) | ||||||
|             speaker.user = user |             speaker.user = user | ||||||
|  | @ -65,7 +65,7 @@ def speaker_create_staff(request, pk): | ||||||
|             return redirect("user_list") |             return redirect("user_list") | ||||||
|     else: |     else: | ||||||
|         form = SpeakerForm(initial={"name": user.get_full_name()}) |         form = SpeakerForm(initial={"name": user.get_full_name()}) | ||||||
|      | 
 | ||||||
|     return render(request, "speakers/speaker_create.html", { |     return render(request, "speakers/speaker_create.html", { | ||||||
|         "form": form, |         "form": form, | ||||||
|     }) |     }) | ||||||
|  | @ -88,8 +88,8 @@ def speaker_create_token(request, token): | ||||||
|             ).update( |             ).update( | ||||||
|                 speaker=existing_speaker |                 speaker=existing_speaker | ||||||
|             ) |             ) | ||||||
|             messages.info(request, "You have been associated with all pending " |             messages.info(request, ("You have been associated with all pending " | ||||||
|                 "talk proposals") |                                     "talk proposals")) | ||||||
|             return redirect("dashboard") |             return redirect("dashboard") | ||||||
|     else: |     else: | ||||||
|         if not request.user.is_authenticated(): |         if not request.user.is_authenticated(): | ||||||
|  | @ -109,7 +109,7 @@ def speaker_edit(request, pk=None): | ||||||
|             speaker = get_object_or_404(Speaker, pk=pk) |             speaker = get_object_or_404(Speaker, pk=pk) | ||||||
|         else: |         else: | ||||||
|             raise Http404() |             raise Http404() | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         form = SpeakerForm(request.POST, request.FILES, instance=speaker) |         form = SpeakerForm(request.POST, request.FILES, instance=speaker) | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|  | @ -118,7 +118,7 @@ def speaker_edit(request, pk=None): | ||||||
|             return redirect("dashboard") |             return redirect("dashboard") | ||||||
|     else: |     else: | ||||||
|         form = SpeakerForm(instance=speaker) |         form = SpeakerForm(instance=speaker) | ||||||
|      | 
 | ||||||
|     return render(request, "speakers/speaker_edit.html", { |     return render(request, "speakers/speaker_edit.html", { | ||||||
|         "form": form, |         "form": form, | ||||||
|     }) |     }) | ||||||
|  | @ -129,7 +129,7 @@ def speaker_profile(request, pk): | ||||||
|     presentations = speaker.all_presentations |     presentations = speaker.all_presentations | ||||||
|     if not presentations and not request.user.is_staff: |     if not presentations and not request.user.is_staff: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     return render(request, "speakers/speaker_profile.html", { |     return render(request, "speakers/speaker_profile.html", { | ||||||
|         "speaker": speaker, |         "speaker": speaker, | ||||||
|         "presentations": presentations, |         "presentations": presentations, | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| from django.contrib import admin | from django.contrib import admin | ||||||
| 
 | 
 | ||||||
| from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, SponsorBenefit | from symposion.sponsorship.models import SponsorLevel, Sponsor, Benefit, BenefitLevel, \ | ||||||
|  |     SponsorBenefit | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BenefitLevelInline(admin.TabularInline): | class BenefitLevelInline(admin.TabularInline): | ||||||
|  | @ -24,7 +25,7 @@ class SponsorBenefitInline(admin.StackedInline): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SponsorAdmin(admin.ModelAdmin): | class SponsorAdmin(admin.ModelAdmin): | ||||||
|      | 
 | ||||||
|     save_on_top = True |     save_on_top = True | ||||||
|     fieldsets = [ |     fieldsets = [ | ||||||
|         (None, { |         (None, { | ||||||
|  | @ -43,7 +44,7 @@ class SponsorAdmin(admin.ModelAdmin): | ||||||
|     ] |     ] | ||||||
|     inlines = [SponsorBenefitInline] |     inlines = [SponsorBenefitInline] | ||||||
|     list_display = ["name", "external_url", "level", "active"] |     list_display = ["name", "external_url", "level", "active"] | ||||||
|      | 
 | ||||||
|     def get_form(self, *args, **kwargs): |     def get_form(self, *args, **kwargs): | ||||||
|         # @@@ kinda ugly but using choices= on NullBooleanField is broken |         # @@@ kinda ugly but using choices= on NullBooleanField is broken | ||||||
|         form = super(SponsorAdmin, self).get_form(*args, **kwargs) |         form = super(SponsorAdmin, self).get_form(*args, **kwargs) | ||||||
|  | @ -56,13 +57,13 @@ class SponsorAdmin(admin.ModelAdmin): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BenefitAdmin(admin.ModelAdmin): | class BenefitAdmin(admin.ModelAdmin): | ||||||
|      | 
 | ||||||
|     list_display = ["name", "type", "description"] |     list_display = ["name", "type", "description"] | ||||||
|     inlines = [BenefitLevelInline] |     inlines = [BenefitLevelInline] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SponsorLevelAdmin(admin.ModelAdmin): | class SponsorLevelAdmin(admin.ModelAdmin): | ||||||
|      | 
 | ||||||
|     inlines = [BenefitLevelInline] |     inlines = [BenefitLevelInline] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ class SponsorApplicationForm(forms.ModelForm): | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|         super(SponsorApplicationForm, self).__init__(*args, **kwargs) |         super(SponsorApplicationForm, self).__init__(*args, **kwargs) | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         model = Sponsor |         model = Sponsor | ||||||
|         fields = [ |         fields = [ | ||||||
|  | @ -26,7 +26,7 @@ class SponsorApplicationForm(forms.ModelForm): | ||||||
|             "contact_email", |             "contact_email", | ||||||
|             "level" |             "level" | ||||||
|         ] |         ] | ||||||
|      | 
 | ||||||
|     def save(self, commit=True): |     def save(self, commit=True): | ||||||
|         obj = super(SponsorApplicationForm, self).save(commit=False) |         obj = super(SponsorApplicationForm, self).save(commit=False) | ||||||
|         obj.applicant = self.user |         obj.applicant = self.user | ||||||
|  | @ -47,26 +47,26 @@ class SponsorDetailsForm(forms.ModelForm): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SponsorBenefitsInlineFormSet(BaseInlineFormSet): | class SponsorBenefitsInlineFormSet(BaseInlineFormSet): | ||||||
|      | 
 | ||||||
|     def _construct_form(self, i, **kwargs): |     def _construct_form(self, i, **kwargs): | ||||||
|         form = super(SponsorBenefitsInlineFormSet, self)._construct_form(i, **kwargs) |         form = super(SponsorBenefitsInlineFormSet, self)._construct_form(i, **kwargs) | ||||||
|          | 
 | ||||||
|         # only include the relevant data fields for this benefit type |         # only include the relevant data fields for this benefit type | ||||||
|         fields = form.instance.data_fields() |         fields = form.instance.data_fields() | ||||||
|         form.fields = dict((k, v) for (k, v) in form.fields.items() if k in fields + ["id"]) |         form.fields = dict((k, v) for (k, v) in form.fields.items() if k in fields + ["id"]) | ||||||
|          | 
 | ||||||
|         for field in fields: |         for field in fields: | ||||||
|             # don't need a label, the form template will label it with the benefit name |             # don't need a label, the form template will label it with the benefit name | ||||||
|             form.fields[field].label = "" |             form.fields[field].label = "" | ||||||
|              | 
 | ||||||
|             # provide word limit as help_text |             # provide word limit as help_text | ||||||
|             if form.instance.benefit.type == "text" and form.instance.max_words: |             if form.instance.benefit.type == "text" and form.instance.max_words: | ||||||
|                 form.fields[field].help_text = u"maximum %s words" % form.instance.max_words |                 form.fields[field].help_text = u"maximum %s words" % form.instance.max_words | ||||||
|              | 
 | ||||||
|             # use admin file widget that shows currently uploaded file |             # use admin file widget that shows currently uploaded file | ||||||
|             if field == "upload": |             if field == "upload": | ||||||
|                 form.fields[field].widget = AdminFileWidget() |                 form.fields[field].widget = AdminFileWidget() | ||||||
|          | 
 | ||||||
|         return form |         return form | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,15 +1,13 @@ | ||||||
| from django.core.management.base import BaseCommand | from django.core.management.base import BaseCommand | ||||||
| 
 | 
 | ||||||
| from django.contrib.auth.models import Group | from symposion.sponsorship.models import Sponsor, SponsorBenefit, SponsorLevel | ||||||
| 
 |  | ||||||
| from symposion.sponsorship.models import Sponsor, SponsorBenefit |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Command(BaseCommand): | class Command(BaseCommand): | ||||||
|      | 
 | ||||||
|     def handle(self, *args, **options): |     def handle(self, *args, **options): | ||||||
|         for sponsor in Sponsor.objects.all(): |         for sponsor in Sponsor.objects.all(): | ||||||
|             level = None   |             level = None | ||||||
|             try: |             try: | ||||||
|                 level = sponsor.level |                 level = sponsor.level | ||||||
|             except SponsorLevel.DoesNotExist: |             except SponsorLevel.DoesNotExist: | ||||||
|  | @ -19,17 +17,17 @@ class Command(BaseCommand): | ||||||
|                     # Create all needed benefits if they don't exist already |                     # Create all needed benefits if they don't exist already | ||||||
|                     sponsor_benefit, created = SponsorBenefit.objects.get_or_create( |                     sponsor_benefit, created = SponsorBenefit.objects.get_or_create( | ||||||
|                         sponsor=sponsor, benefit=benefit_level.benefit) |                         sponsor=sponsor, benefit=benefit_level.benefit) | ||||||
|                      | 
 | ||||||
|                     if created: |                     if created: | ||||||
|                         print "created", sponsor_benefit, "for", sponsor |                         print "created", sponsor_benefit, "for", sponsor | ||||||
|                      | 
 | ||||||
|                     # and set to default limits for this level. |                     # and set to default limits for this level. | ||||||
|                     sponsor_benefit.max_words = benefit_level.max_words |                     sponsor_benefit.max_words = benefit_level.max_words | ||||||
|                     sponsor_benefit.other_limits = benefit_level.other_limits |                     sponsor_benefit.other_limits = benefit_level.other_limits | ||||||
|                      | 
 | ||||||
|                     # and set to active |                     # and set to active | ||||||
|                     sponsor_benefit.active = True |                     sponsor_benefit.active = True | ||||||
|                      | 
 | ||||||
|                     # @@@ We don't call sponsor_benefit.clean here. This means |                     # @@@ We don't call sponsor_benefit.clean here. This means | ||||||
|                     # that if the sponsorship level for a sponsor is adjusted |                     # that if the sponsorship level for a sponsor is adjusted | ||||||
|                     # downwards, an existing too-long text entry can remain, |                     # downwards, an existing too-long text entry can remain, | ||||||
|  |  | ||||||
|  | @ -14,29 +14,30 @@ from symposion.sponsorship.managers import SponsorManager | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SponsorLevel(models.Model): | class SponsorLevel(models.Model): | ||||||
|      | 
 | ||||||
|     conference = models.ForeignKey(Conference, verbose_name=_("conference")) |     conference = models.ForeignKey(Conference, verbose_name=_("conference")) | ||||||
|     name = models.CharField(_("name"), max_length=100) |     name = models.CharField(_("name"), max_length=100) | ||||||
|     order = models.IntegerField(_("order"), default=0) |     order = models.IntegerField(_("order"), default=0) | ||||||
|     cost = models.PositiveIntegerField(_("cost")) |     cost = models.PositiveIntegerField(_("cost")) | ||||||
|     description = models.TextField(_("description"), blank=True, help_text=_("This is private.")) |     description = models.TextField(_("description"), blank=True, help_text=_("This is private.")) | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         ordering = ["conference", "order"] |         ordering = ["conference", "order"] | ||||||
|         verbose_name = _("sponsor level") |         verbose_name = _("sponsor level") | ||||||
|         verbose_name_plural = _("sponsor levels") |         verbose_name_plural = _("sponsor levels") | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.name |         return self.name | ||||||
|      | 
 | ||||||
|     def sponsors(self): |     def sponsors(self): | ||||||
|         return self.sponsor_set.filter(active=True).order_by("added") |         return self.sponsor_set.filter(active=True).order_by("added") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Sponsor(models.Model): | class Sponsor(models.Model): | ||||||
|      | 
 | ||||||
|     applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), null=True) |     applicant = models.ForeignKey(User, related_name="sponsorships", verbose_name=_("applicant"), | ||||||
|      |                                   null=True) | ||||||
|  | 
 | ||||||
|     name = models.CharField(_("Sponsor Name"), max_length=100) |     name = models.CharField(_("Sponsor Name"), max_length=100) | ||||||
|     external_url = models.URLField(_("external URL")) |     external_url = models.URLField(_("external URL")) | ||||||
|     annotation = models.TextField(_("annotation"), blank=True) |     annotation = models.TextField(_("annotation"), blank=True) | ||||||
|  | @ -45,34 +46,36 @@ class Sponsor(models.Model): | ||||||
|     level = models.ForeignKey(SponsorLevel, verbose_name=_("level")) |     level = models.ForeignKey(SponsorLevel, verbose_name=_("level")) | ||||||
|     added = models.DateTimeField(_("added"), default=datetime.datetime.now) |     added = models.DateTimeField(_("added"), default=datetime.datetime.now) | ||||||
|     active = models.BooleanField(_("active"), default=False) |     active = models.BooleanField(_("active"), default=False) | ||||||
|      | 
 | ||||||
|     # Denormalization (this assumes only one logo) |     # Denormalization (this assumes only one logo) | ||||||
|     sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True, editable=False) |     sponsor_logo = models.ForeignKey("SponsorBenefit", related_name="+", null=True, blank=True, | ||||||
|      |                                      editable=False) | ||||||
|  | 
 | ||||||
|     objects = SponsorManager() |     objects = SponsorManager() | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.name |         return self.name | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         verbose_name = _("sponsor") |         verbose_name = _("sponsor") | ||||||
|         verbose_name_plural = _("sponsors") |         verbose_name_plural = _("sponsors") | ||||||
|      | 
 | ||||||
|     def get_absolute_url(self): |     def get_absolute_url(self): | ||||||
|         if self.active: |         if self.active: | ||||||
|             return reverse("sponsor_detail", kwargs={"pk": self.pk}) |             return reverse("sponsor_detail", kwargs={"pk": self.pk}) | ||||||
|         return reverse("sponsor_list") |         return reverse("sponsor_list") | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def website_logo(self): |     def website_logo(self): | ||||||
|         if self.sponsor_logo is None: |         if self.sponsor_logo is None: | ||||||
|             benefits = self.sponsor_benefits.filter(benefit__type="weblogo", upload__isnull=False)[:1] |             benefits = self.sponsor_benefits.filter( | ||||||
|  |                 benefit__type="weblogo", upload__isnull=False)[:1] | ||||||
|             if benefits.count(): |             if benefits.count(): | ||||||
|                 if benefits[0].upload: |                 if benefits[0].upload: | ||||||
|                     self.sponsor_logo = benefits[0] |                     self.sponsor_logo = benefits[0] | ||||||
|                     self.save() |                     self.save() | ||||||
|         return self.sponsor_logo.upload |         return self.sponsor_logo.upload | ||||||
|      | 
 | ||||||
|     @property |     @property | ||||||
|     def listing_text(self): |     def listing_text(self): | ||||||
|         if not hasattr(self, "_listing_text"): |         if not hasattr(self, "_listing_text"): | ||||||
|  | @ -82,46 +85,47 @@ class Sponsor(models.Model): | ||||||
|             if benefits.count(): |             if benefits.count(): | ||||||
|                 self._listing_text = benefits[0].text |                 self._listing_text = benefits[0].text | ||||||
|         return self._listing_text |         return self._listing_text | ||||||
|      | 
 | ||||||
|     def reset_benefits(self): |     def reset_benefits(self): | ||||||
|         """ |         """ | ||||||
|         Reset all benefits for this sponsor to the defaults for their |         Reset all benefits for this sponsor to the defaults for their | ||||||
|         sponsorship level. |         sponsorship level. | ||||||
|         """ |         """ | ||||||
|         level = None |         level = None | ||||||
|          | 
 | ||||||
|         try: |         try: | ||||||
|             level = self.level |             level = self.level | ||||||
|         except SponsorLevel.DoesNotExist: |         except SponsorLevel.DoesNotExist: | ||||||
|             pass |             pass | ||||||
|          | 
 | ||||||
|         allowed_benefits = [] |         allowed_benefits = [] | ||||||
|         if level: |         if level: | ||||||
|             for benefit_level in level.benefit_levels.all(): |             for benefit_level in level.benefit_levels.all(): | ||||||
|                 # Create all needed benefits if they don't exist already |                 # Create all needed benefits if they don't exist already | ||||||
|                 sponsor_benefit, created = SponsorBenefit.objects.get_or_create( |                 sponsor_benefit, created = SponsorBenefit.objects.get_or_create( | ||||||
|                     sponsor=self, benefit=benefit_level.benefit) |                     sponsor=self, benefit=benefit_level.benefit) | ||||||
|                  | 
 | ||||||
|                 # and set to default limits for this level. |                 # and set to default limits for this level. | ||||||
|                 sponsor_benefit.max_words = benefit_level.max_words |                 sponsor_benefit.max_words = benefit_level.max_words | ||||||
|                 sponsor_benefit.other_limits = benefit_level.other_limits |                 sponsor_benefit.other_limits = benefit_level.other_limits | ||||||
|                  | 
 | ||||||
|                 # and set to active |                 # and set to active | ||||||
|                 sponsor_benefit.active = True |                 sponsor_benefit.active = True | ||||||
|                  | 
 | ||||||
|                 # @@@ We don't call sponsor_benefit.clean here. This means |                 # @@@ We don't call sponsor_benefit.clean here. This means | ||||||
|                 # that if the sponsorship level for a sponsor is adjusted |                 # that if the sponsorship level for a sponsor is adjusted | ||||||
|                 # downwards, an existing too-long text entry can remain, |                 # downwards, an existing too-long text entry can remain, | ||||||
|                 # and won't raise a validation error until it's next |                 # and won't raise a validation error until it's next | ||||||
|                 # edited. |                 # edited. | ||||||
|                 sponsor_benefit.save() |                 sponsor_benefit.save() | ||||||
|                  | 
 | ||||||
|                 allowed_benefits.append(sponsor_benefit.pk) |                 allowed_benefits.append(sponsor_benefit.pk) | ||||||
|          | 
 | ||||||
|         # Any remaining sponsor benefits that don't normally belong to |         # Any remaining sponsor benefits that don't normally belong to | ||||||
|         # this level are set to inactive |         # this level are set to inactive | ||||||
|         self.sponsor_benefits.exclude(pk__in=allowed_benefits).update(active=False, max_words=None, other_limits="") |         self.sponsor_benefits.exclude(pk__in=allowed_benefits)\ | ||||||
|      |             .update(active=False, max_words=None, other_limits="") | ||||||
|  | 
 | ||||||
|     def send_coordinator_emails(self): |     def send_coordinator_emails(self): | ||||||
|         pass  # @@@ should this just be done centrally? |         pass  # @@@ should this just be done centrally? | ||||||
| 
 | 
 | ||||||
|  | @ -147,59 +151,60 @@ BENEFIT_TYPE_CHOICES = [ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Benefit(models.Model): | class Benefit(models.Model): | ||||||
|      | 
 | ||||||
|     name = models.CharField(_("name"), max_length=100) |     name = models.CharField(_("name"), max_length=100) | ||||||
|     description = models.TextField(_("description"), blank=True) |     description = models.TextField(_("description"), blank=True) | ||||||
|     type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES, max_length=10, default="simple") |     type = models.CharField(_("type"), choices=BENEFIT_TYPE_CHOICES, max_length=10, | ||||||
|      |                             default="simple") | ||||||
|  | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.name |         return self.name | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BenefitLevel(models.Model): | class BenefitLevel(models.Model): | ||||||
|      | 
 | ||||||
|     benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit")) |     benefit = models.ForeignKey(Benefit, related_name="benefit_levels", verbose_name=_("benefit")) | ||||||
|     level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("level")) |     level = models.ForeignKey(SponsorLevel, related_name="benefit_levels", verbose_name=_("level")) | ||||||
|      | 
 | ||||||
|     # default limits for this benefit at given level |     # default limits for this benefit at given level | ||||||
|     max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) |     max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) | ||||||
|     other_limits = models.CharField(_("other limits"), max_length=200, blank=True) |     other_limits = models.CharField(_("other limits"), max_length=200, blank=True) | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         ordering = ["level"] |         ordering = ["level"] | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return u"%s - %s" % (self.level, self.benefit) |         return u"%s - %s" % (self.level, self.benefit) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SponsorBenefit(models.Model): | class SponsorBenefit(models.Model): | ||||||
|      | 
 | ||||||
|     sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor")) |     sponsor = models.ForeignKey(Sponsor, related_name="sponsor_benefits", verbose_name=_("sponsor")) | ||||||
|     benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("benefit")) |     benefit = models.ForeignKey(Benefit, related_name="sponsor_benefits", verbose_name=_("benefit")) | ||||||
|     active = models.BooleanField(default=True) |     active = models.BooleanField(default=True) | ||||||
|      | 
 | ||||||
|     # Limits: will initially be set to defaults from corresponding BenefitLevel |     # Limits: will initially be set to defaults from corresponding BenefitLevel | ||||||
|     max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) |     max_words = models.PositiveIntegerField(_("max words"), blank=True, null=True) | ||||||
|     other_limits = models.CharField(_("other limits"), max_length=200, blank=True) |     other_limits = models.CharField(_("other limits"), max_length=200, blank=True) | ||||||
|      | 
 | ||||||
|     # Data: zero or one of these fields will be used, depending on the |     # Data: zero or one of these fields will be used, depending on the | ||||||
|     # type of the Benefit (text, file, or simple) |     # type of the Benefit (text, file, or simple) | ||||||
|     text = models.TextField(_("text"), blank=True) |     text = models.TextField(_("text"), blank=True) | ||||||
|     upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files") |     upload = models.FileField(_("file"), blank=True, upload_to="sponsor_files") | ||||||
|      | 
 | ||||||
|     class Meta: |     class Meta: | ||||||
|         ordering = ["-active"] |         ordering = ["-active"] | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return u"%s - %s" % (self.sponsor, self.benefit) |         return u"%s - %s" % (self.sponsor, self.benefit) | ||||||
|      | 
 | ||||||
|     def clean(self): |     def clean(self): | ||||||
|         num_words = len(self.text.split()) |         num_words = len(self.text.split()) | ||||||
|         if self.max_words and num_words > self.max_words: |         if self.max_words and num_words > self.max_words: | ||||||
|             raise ValidationError( |             raise ValidationError( | ||||||
|                 "Sponsorship level only allows for %s words, you provided %d." % ( |                 "Sponsorship level only allows for %s words, you provided %d." % ( | ||||||
|                     self.max_words, num_words)) |                     self.max_words, num_words)) | ||||||
|      | 
 | ||||||
|     def data_fields(self): |     def data_fields(self): | ||||||
|         """ |         """ | ||||||
|         Return list of data field names which should be editable for |         Return list of data field names which should be editable for | ||||||
|  |  | ||||||
|  | @ -8,7 +8,7 @@ register = template.Library() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SponsorsNode(template.Node): | class SponsorsNode(template.Node): | ||||||
|      | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def handle_token(cls, parser, token): |     def handle_token(cls, parser, token): | ||||||
|         bits = token.split_contents() |         bits = token.split_contents() | ||||||
|  | @ -18,27 +18,30 @@ class SponsorsNode(template.Node): | ||||||
|             return cls(bits[3], bits[1]) |             return cls(bits[3], bits[1]) | ||||||
|         else: |         else: | ||||||
|             raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0]) |             raise template.TemplateSyntaxError("%r takes 'as var' or 'level as var'" % bits[0]) | ||||||
|      | 
 | ||||||
|     def __init__(self, context_var, level=None): |     def __init__(self, context_var, level=None): | ||||||
|         if level: |         if level: | ||||||
|             self.level = template.Variable(level) |             self.level = template.Variable(level) | ||||||
|         else: |         else: | ||||||
|             self.level = None |             self.level = None | ||||||
|         self.context_var = context_var |         self.context_var = context_var | ||||||
|      | 
 | ||||||
|     def render(self, context): |     def render(self, context): | ||||||
|         conference = current_conference() |         conference = current_conference() | ||||||
|         if self.level: |         if self.level: | ||||||
|             level = self.level.resolve(context) |             level = self.level.resolve(context) | ||||||
|             queryset = Sponsor.objects.filter(level__conference = conference, level__name__iexact = level, active = True).order_by("added") |             queryset = Sponsor.objects.filter( | ||||||
|  |                 level__conference=conference, level__name__iexact=level, active=True)\ | ||||||
|  |                 .order_by("added") | ||||||
|         else: |         else: | ||||||
|             queryset = Sponsor.objects.filter(level__conference = conference, active = True).order_by("level__order", "added") |             queryset = Sponsor.objects.filter(level__conference=conference, active=True)\ | ||||||
|  |                 .order_by("level__order", "added") | ||||||
|         context[self.context_var] = queryset |         context[self.context_var] = queryset | ||||||
|         return u"" |         return u"" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SponsorLevelNode(template.Node): | class SponsorLevelNode(template.Node): | ||||||
|      | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def handle_token(cls, parser, token): |     def handle_token(cls, parser, token): | ||||||
|         bits = token.split_contents() |         bits = token.split_contents() | ||||||
|  | @ -46,10 +49,10 @@ class SponsorLevelNode(template.Node): | ||||||
|             return cls(bits[2]) |             return cls(bits[2]) | ||||||
|         else: |         else: | ||||||
|             raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) |             raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) | ||||||
|      | 
 | ||||||
|     def __init__(self, context_var): |     def __init__(self, context_var): | ||||||
|         self.context_var = context_var |         self.context_var = context_var | ||||||
|      | 
 | ||||||
|     def render(self, context): |     def render(self, context): | ||||||
|         conference = current_conference() |         conference = current_conference() | ||||||
|         context[self.context_var] = SponsorLevel.objects.filter(conference=conference) |         context[self.context_var] = SponsorLevel.objects.filter(conference=conference) | ||||||
|  | @ -72,4 +75,3 @@ def sponsor_levels(parser, token): | ||||||
|     {% sponsor_levels as levels %} |     {% sponsor_levels as levels %} | ||||||
|     """ |     """ | ||||||
|     return SponsorLevelNode.handle_token(parser, token) |     return SponsorLevelNode.handle_token(parser, token) | ||||||
|      |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # flake8: noqa | ||||||
| from django.conf.urls.defaults import patterns, url | from django.conf.urls.defaults import patterns, url | ||||||
| from django.views.generic.simple import direct_to_template | from django.views.generic.simple import direct_to_template | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,7 +5,8 @@ from django.template import RequestContext | ||||||
| from django.contrib import messages | from django.contrib import messages | ||||||
| from django.contrib.auth.decorators import login_required | from django.contrib.auth.decorators import login_required | ||||||
| 
 | 
 | ||||||
| from symposion.sponsorship.forms import SponsorApplicationForm, SponsorDetailsForm, SponsorBenefitsFormSet | from symposion.sponsorship.forms import SponsorApplicationForm, SponsorDetailsForm, \ | ||||||
|  |     SponsorBenefitsFormSet | ||||||
| from symposion.sponsorship.models import Sponsor, SponsorBenefit | from symposion.sponsorship.models import Sponsor, SponsorBenefit | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -18,7 +19,7 @@ def sponsor_apply(request): | ||||||
|             return redirect("sponsor_detail", pk=sponsor.pk) |             return redirect("sponsor_detail", pk=sponsor.pk) | ||||||
|     else: |     else: | ||||||
|         form = SponsorApplicationForm(user=request.user) |         form = SponsorApplicationForm(user=request.user) | ||||||
|      | 
 | ||||||
|     return render_to_response("sponsorship/apply.html", { |     return render_to_response("sponsorship/apply.html", { | ||||||
|         "form": form, |         "form": form, | ||||||
|     }, context_instance=RequestContext(request)) |     }, context_instance=RequestContext(request)) | ||||||
|  | @ -28,7 +29,7 @@ def sponsor_apply(request): | ||||||
| def sponsor_add(request): | def sponsor_add(request): | ||||||
|     if not request.user.is_staff: |     if not request.user.is_staff: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|         form = SponsorApplicationForm(request.POST, user=request.user) |         form = SponsorApplicationForm(request.POST, user=request.user) | ||||||
|         if form.is_valid(): |         if form.is_valid(): | ||||||
|  | @ -38,7 +39,7 @@ def sponsor_add(request): | ||||||
|             return redirect("sponsor_detail", pk=sponsor.pk) |             return redirect("sponsor_detail", pk=sponsor.pk) | ||||||
|     else: |     else: | ||||||
|         form = SponsorApplicationForm(user=request.user) |         form = SponsorApplicationForm(user=request.user) | ||||||
|      | 
 | ||||||
|     return render_to_response("sponsorship/add.html", { |     return render_to_response("sponsorship/add.html", { | ||||||
|         "form": form, |         "form": form, | ||||||
|     }, context_instance=RequestContext(request)) |     }, context_instance=RequestContext(request)) | ||||||
|  | @ -47,31 +48,31 @@ def sponsor_add(request): | ||||||
| @login_required | @login_required | ||||||
| def sponsor_detail(request, pk): | def sponsor_detail(request, pk): | ||||||
|     sponsor = get_object_or_404(Sponsor, pk=pk) |     sponsor = get_object_or_404(Sponsor, pk=pk) | ||||||
|      | 
 | ||||||
|     if sponsor.applicant != request.user: |     if sponsor.applicant != request.user: | ||||||
|         return redirect("sponsor_list") |         return redirect("sponsor_list") | ||||||
|      | 
 | ||||||
|     formset_kwargs = { |     formset_kwargs = { | ||||||
|         "instance": sponsor, |         "instance": sponsor, | ||||||
|         "queryset": SponsorBenefit.objects.filter(active=True) |         "queryset": SponsorBenefit.objects.filter(active=True) | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     if request.method == "POST": |     if request.method == "POST": | ||||||
|          | 
 | ||||||
|         form = SponsorDetailsForm(request.POST, instance=sponsor) |         form = SponsorDetailsForm(request.POST, instance=sponsor) | ||||||
|         formset = SponsorBenefitsFormSet(request.POST, request.FILES, **formset_kwargs) |         formset = SponsorBenefitsFormSet(request.POST, request.FILES, **formset_kwargs) | ||||||
|          | 
 | ||||||
|         if form.is_valid() and formset.is_valid(): |         if form.is_valid() and formset.is_valid(): | ||||||
|             form.save() |             form.save() | ||||||
|             formset.save() |             formset.save() | ||||||
|              | 
 | ||||||
|             messages.success(request, "Sponsorship details have been updated") |             messages.success(request, "Sponsorship details have been updated") | ||||||
|              | 
 | ||||||
|             return redirect("dashboard") |             return redirect("dashboard") | ||||||
|     else: |     else: | ||||||
|         form = SponsorDetailsForm(instance=sponsor) |         form = SponsorDetailsForm(instance=sponsor) | ||||||
|         formset = SponsorBenefitsFormSet(**formset_kwargs) |         formset = SponsorBenefitsFormSet(**formset_kwargs) | ||||||
|      | 
 | ||||||
|     return render_to_response("sponsorship/detail.html", { |     return render_to_response("sponsorship/detail.html", { | ||||||
|         "sponsor": sponsor, |         "sponsor": sponsor, | ||||||
|         "form": form, |         "form": form, | ||||||
|  |  | ||||||
|  | @ -5,8 +5,7 @@ import reversion | ||||||
| from symposion.teams.models import Team, Membership | from symposion.teams.models import Team, Membership | ||||||
| 
 | 
 | ||||||
| admin.site.register(Team, | admin.site.register(Team, | ||||||
|     prepopulated_fields={"slug": ("name",)}, |                     prepopulated_fields={"slug": ("name",)}) | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MembershipAdmin(reversion.VersionAdmin): | class MembershipAdmin(reversion.VersionAdmin): | ||||||
|  |  | ||||||
|  | @ -4,10 +4,10 @@ from .models import Team | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TeamPermissionsBackend(object): | class TeamPermissionsBackend(object): | ||||||
|      | 
 | ||||||
|     def authenticate(self, username=None, password=None): |     def authenticate(self, username=None, password=None): | ||||||
|         return None |         return None | ||||||
|      | 
 | ||||||
|     def get_team_permissions(self, user_obj, obj=None): |     def get_team_permissions(self, user_obj, obj=None): | ||||||
|         """ |         """ | ||||||
|         Returns a set of permission strings that this user has through his/her |         Returns a set of permission strings that this user has through his/her | ||||||
|  |  | ||||||
|  | @ -9,40 +9,43 @@ from symposion.teams.models import Membership | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TeamInvitationForm(forms.Form): | class TeamInvitationForm(forms.Form): | ||||||
|      | 
 | ||||||
|     email = forms.EmailField(help_text="email address must be that of an account on this conference site") |     email = forms.EmailField(help_text=("email address must be that of an account on this " | ||||||
|      |                                         "conference site")) | ||||||
|  | 
 | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         self.team = kwargs.pop("team") |         self.team = kwargs.pop("team") | ||||||
|         super(TeamInvitationForm, self).__init__(*args, **kwargs) |         super(TeamInvitationForm, self).__init__(*args, **kwargs) | ||||||
|      | 
 | ||||||
|     def clean(self): |     def clean(self): | ||||||
|         cleaned_data = super(TeamInvitationForm, self).clean() |         cleaned_data = super(TeamInvitationForm, self).clean() | ||||||
|         email = cleaned_data.get("email") |         email = cleaned_data.get("email") | ||||||
|          | 
 | ||||||
|         if email is None: |         if email is None: | ||||||
|             raise forms.ValidationError("valid email address required") |             raise forms.ValidationError("valid email address required") | ||||||
|          | 
 | ||||||
|         try: |         try: | ||||||
|             user = User.objects.get(email=email) |             user = User.objects.get(email=email) | ||||||
|         except User.DoesNotExist: |         except User.DoesNotExist: | ||||||
|             # eventually we can invite them but for now assume they are |             # eventually we can invite them but for now assume they are | ||||||
|             # already on the site |             # already on the site | ||||||
|             raise forms.ValidationError(mark_safe("no account with email address <b>%s</b> found on this conference site" % escape(email))) |             raise forms.ValidationError( | ||||||
|          |                 mark_safe("no account with email address <b>%s</b> found on this conference " | ||||||
|  |                           "site" % escape(email))) | ||||||
|  | 
 | ||||||
|         state = self.team.get_state_for_user(user) |         state = self.team.get_state_for_user(user) | ||||||
|          | 
 | ||||||
|         if state in ["member", "manager"]: |         if state in ["member", "manager"]: | ||||||
|             raise forms.ValidationError("user already in team") |             raise forms.ValidationError("user already in team") | ||||||
|          | 
 | ||||||
|         if state in ["invited"]: |         if state in ["invited"]: | ||||||
|             raise forms.ValidationError("user already invited to team") |             raise forms.ValidationError("user already invited to team") | ||||||
|          | 
 | ||||||
|         self.user = user |         self.user = user | ||||||
|         self.state = state |         self.state = state | ||||||
|          | 
 | ||||||
|         return cleaned_data |         return cleaned_data | ||||||
|      | 
 | ||||||
|     def invite(self): |     def invite(self): | ||||||
|         if self.state is None: |         if self.state is None: | ||||||
|             Membership.objects.create(team=self.team, user=self.user, state="invited") |             Membership.objects.create(team=self.team, user=self.user, state="invited") | ||||||
|  |  | ||||||
|  | @ -20,19 +20,20 @@ class Team(models.Model): | ||||||
|     name = models.CharField(max_length=100) |     name = models.CharField(max_length=100) | ||||||
|     description = models.TextField(blank=True) |     description = models.TextField(blank=True) | ||||||
|     access = models.CharField(max_length=20, choices=TEAM_ACCESS_CHOICES) |     access = models.CharField(max_length=20, choices=TEAM_ACCESS_CHOICES) | ||||||
|      | 
 | ||||||
|     # member permissions |     # member permissions | ||||||
|     permissions = models.ManyToManyField(Permission, blank=True, related_name="member_teams") |     permissions = models.ManyToManyField(Permission, blank=True, related_name="member_teams") | ||||||
|      | 
 | ||||||
|     # manager permissions |     # manager permissions | ||||||
|     manager_permissions = models.ManyToManyField(Permission, blank=True, related_name="manager_teams") |     manager_permissions = models.ManyToManyField(Permission, blank=True, | ||||||
|      |                                                  related_name="manager_teams") | ||||||
|  | 
 | ||||||
|     created = models.DateTimeField(default=datetime.datetime.now, editable=False) |     created = models.DateTimeField(default=datetime.datetime.now, editable=False) | ||||||
|      | 
 | ||||||
|     @models.permalink |     @models.permalink | ||||||
|     def get_absolute_url(self): |     def get_absolute_url(self): | ||||||
|         return ("team_detail", [self.slug]) |         return ("team_detail", [self.slug]) | ||||||
|      | 
 | ||||||
|     def __unicode__(self): |     def __unicode__(self): | ||||||
|         return self.name |         return self.name | ||||||
| 
 | 
 | ||||||
|  | @ -41,16 +42,16 @@ class Team(models.Model): | ||||||
|             return self.memberships.get(user=user).state |             return self.memberships.get(user=user).state | ||||||
|         except Membership.DoesNotExist: |         except Membership.DoesNotExist: | ||||||
|             return None |             return None | ||||||
|      | 
 | ||||||
|     def applicants(self): |     def applicants(self): | ||||||
|         return self.memberships.filter(state="applied") |         return self.memberships.filter(state="applied") | ||||||
|      | 
 | ||||||
|     def invitees(self): |     def invitees(self): | ||||||
|         return self.memberships.filter(state="invited") |         return self.memberships.filter(state="invited") | ||||||
|      | 
 | ||||||
|     def members(self): |     def members(self): | ||||||
|         return self.memberships.filter(state="member") |         return self.memberships.filter(state="member") | ||||||
|      | 
 | ||||||
|     def managers(self): |     def managers(self): | ||||||
|         return self.memberships.filter(state="manager") |         return self.memberships.filter(state="manager") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ register = template.Library() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AvailableTeamsNode(template.Node): | class AvailableTeamsNode(template.Node): | ||||||
|      | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def handle_token(cls, parser, token): |     def handle_token(cls, parser, token): | ||||||
|         bits = token.split_contents() |         bits = token.split_contents() | ||||||
|  | @ -14,10 +14,10 @@ class AvailableTeamsNode(template.Node): | ||||||
|             return cls(bits[2]) |             return cls(bits[2]) | ||||||
|         else: |         else: | ||||||
|             raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) |             raise template.TemplateSyntaxError("%r takes 'as var'" % bits[0]) | ||||||
|      | 
 | ||||||
|     def __init__(self, context_var): |     def __init__(self, context_var): | ||||||
|         self.context_var = context_var |         self.context_var = context_var | ||||||
|      | 
 | ||||||
|     def render(self, context): |     def render(self, context): | ||||||
|         request = context["request"] |         request = context["request"] | ||||||
|         teams = [] |         teams = [] | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # flake8: noqa | ||||||
| from django.conf.urls.defaults import * | from django.conf.urls.defaults import * | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -7,7 +8,7 @@ urlpatterns = patterns("symposion.teams.views", | ||||||
|     url(r"^(?P<slug>[\w\-]+)/join/$", "team_join", name="team_join"), |     url(r"^(?P<slug>[\w\-]+)/join/$", "team_join", name="team_join"), | ||||||
|     url(r"^(?P<slug>[\w\-]+)/leave/$", "team_leave", name="team_leave"), |     url(r"^(?P<slug>[\w\-]+)/leave/$", "team_leave", name="team_leave"), | ||||||
|     url(r"^(?P<slug>[\w\-]+)/apply/$", "team_apply", name="team_apply"), |     url(r"^(?P<slug>[\w\-]+)/apply/$", "team_apply", name="team_apply"), | ||||||
|      | 
 | ||||||
|     # membership specific |     # membership specific | ||||||
|     url(r"^promote/(?P<pk>\d+)/$", "team_promote", name="team_promote"), |     url(r"^promote/(?P<pk>\d+)/$", "team_promote", name="team_promote"), | ||||||
|     url(r"^demote/(?P<pk>\d+)/$", "team_demote", name="team_demote"), |     url(r"^demote/(?P<pk>\d+)/$", "team_demote", name="team_demote"), | ||||||
|  |  | ||||||
|  | @ -10,7 +10,7 @@ from symposion.teams.forms import TeamInvitationForm | ||||||
| from symposion.teams.models import Team, Membership | from symposion.teams.models import Team, Membership | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## perm checks | # perm checks | ||||||
| # | # | ||||||
| # @@@ these can be moved | # @@@ these can be moved | ||||||
| 
 | 
 | ||||||
|  | @ -50,7 +50,7 @@ def can_invite(team, user): | ||||||
|     return False |     return False | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## views | # views | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @login_required | @login_required | ||||||
|  | @ -59,7 +59,7 @@ def team_detail(request, slug): | ||||||
|     state = team.get_state_for_user(request.user) |     state = team.get_state_for_user(request.user) | ||||||
|     if team.access == "invitation" and state is None and not request.user.is_staff: |     if team.access == "invitation" and state is None and not request.user.is_staff: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     if can_invite(team, request.user): |     if can_invite(team, request.user): | ||||||
|         if request.method == "POST": |         if request.method == "POST": | ||||||
|             form = TeamInvitationForm(request.POST, team=team) |             form = TeamInvitationForm(request.POST, team=team) | ||||||
|  | @ -72,7 +72,7 @@ def team_detail(request, slug): | ||||||
|             form = TeamInvitationForm(team=team) |             form = TeamInvitationForm(team=team) | ||||||
|     else: |     else: | ||||||
|         form = None |         form = None | ||||||
|      | 
 | ||||||
|     return render(request, "teams/team_detail.html", { |     return render(request, "teams/team_detail.html", { | ||||||
|         "team": team, |         "team": team, | ||||||
|         "state": state, |         "state": state, | ||||||
|  | @ -89,7 +89,7 @@ def team_join(request, slug): | ||||||
|     state = team.get_state_for_user(request.user) |     state = team.get_state_for_user(request.user) | ||||||
|     if team.access == "invitation" and state is None and not request.user.is_staff: |     if team.access == "invitation" and state is None and not request.user.is_staff: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     if can_join(team, request.user) and request.method == "POST": |     if can_join(team, request.user) and request.method == "POST": | ||||||
|         membership, created = Membership.objects.get_or_create(team=team, user=request.user) |         membership, created = Membership.objects.get_or_create(team=team, user=request.user) | ||||||
|         membership.state = "member" |         membership.state = "member" | ||||||
|  | @ -106,7 +106,7 @@ def team_leave(request, slug): | ||||||
|     state = team.get_state_for_user(request.user) |     state = team.get_state_for_user(request.user) | ||||||
|     if team.access == "invitation" and state is None and not request.user.is_staff: |     if team.access == "invitation" and state is None and not request.user.is_staff: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     if can_leave(team, request.user) and request.method == "POST": |     if can_leave(team, request.user) and request.method == "POST": | ||||||
|         membership = Membership.objects.get(team=team, user=request.user) |         membership = Membership.objects.get(team=team, user=request.user) | ||||||
|         membership.delete() |         membership.delete() | ||||||
|  | @ -122,7 +122,7 @@ def team_apply(request, slug): | ||||||
|     state = team.get_state_for_user(request.user) |     state = team.get_state_for_user(request.user) | ||||||
|     if team.access == "invitation" and state is None and not request.user.is_staff: |     if team.access == "invitation" and state is None and not request.user.is_staff: | ||||||
|         raise Http404() |         raise Http404() | ||||||
|      | 
 | ||||||
|     if can_apply(team, request.user) and request.method == "POST": |     if can_apply(team, request.user) and request.method == "POST": | ||||||
|         membership, created = Membership.objects.get_or_create(team=team, user=request.user) |         membership, created = Membership.objects.get_or_create(team=team, user=request.user) | ||||||
|         membership.state = "applied" |         membership.state = "applied" | ||||||
|  |  | ||||||
|  | @ -7,9 +7,9 @@ from django.contrib.sites.models import Site | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def send_email(to, kind, **kwargs): | def send_email(to, kind, **kwargs): | ||||||
|      | 
 | ||||||
|     current_site = Site.objects.get_current() |     current_site = Site.objects.get_current() | ||||||
|      | 
 | ||||||
|     ctx = { |     ctx = { | ||||||
|         "current_site": current_site, |         "current_site": current_site, | ||||||
|         "STATIC_URL": settings.STATIC_URL, |         "STATIC_URL": settings.STATIC_URL, | ||||||
|  | @ -19,12 +19,12 @@ def send_email(to, kind, **kwargs): | ||||||
|         current_site.name, |         current_site.name, | ||||||
|         render_to_string("emails/%s/subject.txt" % kind, ctx).strip() |         render_to_string("emails/%s/subject.txt" % kind, ctx).strip() | ||||||
|     ) |     ) | ||||||
|      | 
 | ||||||
|     message_html = render_to_string("emails/%s/message.html" % kind, ctx) |     message_html = render_to_string("emails/%s/message.html" % kind, ctx) | ||||||
|     message_plaintext = strip_tags(message_html) |     message_plaintext = strip_tags(message_html) | ||||||
|      | 
 | ||||||
|     from_email = settings.DEFAULT_FROM_EMAIL |     from_email = settings.DEFAULT_FROM_EMAIL | ||||||
|      | 
 | ||||||
|     email = EmailMultiAlternatives(subject, message_plaintext, from_email, to) |     email = EmailMultiAlternatives(subject, message_plaintext, from_email, to) | ||||||
|     email.attach_alternative(message_html, "text/html") |     email.attach_alternative(message_html, "text/html") | ||||||
|     email.send() |     email.send() | ||||||
|  |  | ||||||
|  | @ -12,12 +12,12 @@ import symposion.forms | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SignupView(account.views.SignupView): | class SignupView(account.views.SignupView): | ||||||
|      | 
 | ||||||
|     form_class = symposion.forms.SignupForm |     form_class = symposion.forms.SignupForm | ||||||
|     form_kwargs = { |     form_kwargs = { | ||||||
|         "prefix": "signup", |         "prefix": "signup", | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     def create_user(self, form, commit=True): |     def create_user(self, form, commit=True): | ||||||
|         user_kwargs = { |         user_kwargs = { | ||||||
|             "first_name": form.cleaned_data["first_name"], |             "first_name": form.cleaned_data["first_name"], | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Carlos Henrique Romano
						Carlos Henrique Romano