class NewsPost(Displayable, RichText, AdminThumbMixin): featured_image = FileField(verbose_name="Featured Image", upload_to=upload_to("images", "images"), format="Image", max_length=255, null=True, blank=True) admin_thumb_field = "featured_image" featured_image_wide = FileField(verbose_name="Featured Image (Wide)", upload_to=upload_to("images", "images"), format="Image", max_length=255, null=True, blank=True, help_text="For front-page slider images, please make sure they are at least 785px wide and 400px tall.") marquee_caption = models.CharField(null=True, blank=True, max_length=255) login_required = models.BooleanField("Login required", default=False, help_text="If checked, only logged in users can view this page") parent = None # To make it compatible with the side_menu template children = DummyEmptyResultSet() # To make it compatible with the side_menu template category = ForeignKey('sfpirgapp.Category', related_name='news_posts') in_menus = MenusField("Show in menus", blank=True, null=True) @property def richtextpage(self): return self @models.permalink def get_absolute_url(self): return ('news-post', (), {'news': self.slug}) def in_menu_template(self, template_name): if self.in_menus is not None: for i, l, t in settings.PAGE_MENU_TEMPLATES: if not unicode(i) in self.in_menus and t == template_name: return False return True
def test_menusfield_default(self): my_default = (1, 3) def my_default_func(): return my_default choices = ((1, 'First Menu', 'template1'), (2, 'Second Menu', 'template2'), (3, 'Third Menu', 'template3')) with override_settings(PAGE_MENU_TEMPLATES=choices): with override_settings(PAGE_MENU_TEMPLATES_DEFAULT=(1, 2, 3)): # test default field = MenusField(choices=choices, default=my_default) self.assertTrue(field.has_default()) self.assertEqual(my_default, field.get_default()) # test callable default field = MenusField(choices=choices, default=my_default_func) self.assertTrue(field.has_default()) self.assertEqual(my_default, field.get_default())
class P3(BasePage): in_menus = MenusField(blank=True, null=True)
class Page(BasePage): """ A page in the page tree. This is the base class that custom content types need to subclass. """ parent = models.ForeignKey("Page", blank=True, null=True, related_name="children") in_menus = MenusField(_("Show in menus"), blank=True, null=True) titles = models.CharField(editable=False, max_length=1000, null=True) content_model = models.CharField(editable=False, max_length=50, null=True) login_required = models.BooleanField(_("Login required"), default=False, help_text=_("If checked, only logged in users can view this page")) class Meta: verbose_name = _("Page") verbose_name_plural = _("Pages") ordering = ("titles",) order_with_respect_to = "parent" def __str__(self): return self.titles def get_absolute_url(self): """ URL for a page - for ``Link`` page types, simply return its slug since these don't have an actual URL pattern. Also handle the special case of the homepage being a page object. """ slug = self.slug if self.content_model == "link": # Ensure the URL is absolute. slug = urljoin('/', slug) return slug if slug == "/": return reverse("home") else: return reverse("page", kwargs={"slug": slug}) def save(self, *args, **kwargs): """ Create the titles field using the titles up the parent chain and set the initial value for ordering. """ if self.id is None: self.content_model = self._meta.object_name.lower() titles = [self.title] parent = self.parent while parent is not None: titles.insert(0, parent.title) parent = parent.parent self.titles = " / ".join(titles) super(Page, self).save(*args, **kwargs) def description_from_content(self): """ Override ``Displayable.description_from_content`` to load the content type subclass for when ``save`` is called directly on a ``Page`` instance, so that all fields defined on the subclass are available for generating the description. """ if self.__class__ == Page: content_model = self.get_content_model() if content_model: return content_model.description_from_content() return super(Page, self).description_from_content() def get_ascendants(self, for_user=None): """ Returns the ascendants for the page. Ascendants are cached in the ``_ascendants`` attribute, which is populated when the page is loaded via ``Page.objects.with_ascendants_for_slug``. """ if not self.parent_id: # No parents at all, bail out. return [] if not hasattr(self, "_ascendants"): # _ascendants has not been either page.get_ascendants or # Page.objects.assigned by with_ascendants_for_slug, so # run it to see if we can retrieve all parents in a single # query, which will occur if the slugs for each of the pages # have not been customised. if self.slug: kwargs = {"for_user": for_user} pages = Page.objects.with_ascendants_for_slug(self.slug, **kwargs) self._ascendants = pages[0]._ascendants else: self._ascendants = [] if not self._ascendants: # Page has a parent but with_ascendants_for_slug failed to # find them due to custom slugs, so retrieve the parents # recursively. child = self while child.parent_id is not None: self._ascendants.append(child.parent) child = child.parent return self._ascendants @classmethod def get_content_models(cls): """ Return all Page subclasses. """ is_content_model = lambda m: m is not Page and issubclass(m, Page) return list(filter(is_content_model, models.get_models())) def get_content_model(self): """ Provies a generic method of retrieving the instance of the custom content type's model for this page. """ return getattr(self, self.content_model, None) def get_slug(self): """ Recursively build the slug from the chain of parents. """ slug = slugify(self.title) if self.parent is not None: return "%s/%s" % (self.parent.slug, slug) return slug def set_slug(self, new_slug): """ Changes this page's slug, and all other pages whose slugs start with this page's slug. """ for page in Page.objects.filter(slug__startswith=self.slug): if not page.overridden(): page.slug = new_slug + page.slug[len(self.slug):] page.save() self.slug = new_slug def set_parent(self, new_parent): """ Change the parent of this page, changing this page's slug to match the new parent if necessary. """ self_slug = self.slug old_parent_slug = self.parent.slug if self.parent else "" new_parent_slug = new_parent.slug if new_parent else "" # Make sure setting the new parent won't cause a cycle. parent = new_parent while parent is not None: if parent.pk == self.pk: raise AttributeError("You can't set a page or its child as" " a parent.") parent = parent.parent self.parent = new_parent self.save() if self_slug: if not old_parent_slug: self.set_slug("/".join((new_parent_slug, self.slug))) elif self.slug.startswith(old_parent_slug): new_slug = self.slug.replace(old_parent_slug, new_parent_slug, 1) self.set_slug(new_slug.strip("/")) def overridden(self): """ Returns ``True`` if the page's slug has an explicitly defined urlpattern and is therefore considered to be overridden. """ from mezzanine.pages.views import page page_url = reverse("page", kwargs={"slug": self.slug}) resolved_view = resolve(page_url)[0] return resolved_view != page def can_add(self, request): """ Dynamic ``add`` permission for content types to override. """ return self.slug != "/" def can_change(self, request): """ Dynamic ``change`` permission for content types to override. """ return True def can_delete(self, request): """ Dynamic ``delete`` permission for content types to override. """ return True def set_helpers(self, context): """ Called from the ``page_menu`` template tag and assigns a handful of properties based on the current page, that are used within the various types of menus. """ current_page = context["_current_page"] current_page_id = getattr(current_page, "id", None) current_parent_id = getattr(current_page, "parent_id", None) # Am I a child of the current page? self.is_current_child = self.parent_id == current_page_id self.is_child = self.is_current_child # Backward compatibility # Is my parent the same as the current page's? self.is_current_sibling = self.parent_id == current_parent_id # Am I the current page? try: request = context["request"] except KeyError: # No request context, most likely when tests are run. self.is_current = False else: self.is_current = self.slug == path_to_slug(request.path_info) # Is the current page me or any page up the parent chain? def is_c_or_a(page_id): parent_id = context.get("_parent_page_ids", {}).get(page_id) return self.id == page_id or (parent_id and is_c_or_a(parent_id)) self.is_current_or_ascendant = lambda: bool(is_c_or_a(current_page_id)) self.is_current_parent = self.id == current_parent_id # Am I a primary page? self.is_primary = self.parent_id is None # What's an ID I can use in HTML? self.html_id = self.slug.replace("/", "-") # Default branch level - gets assigned in the page_menu tag. self.branch_level = 0 def in_menu_template(self, template_name): if self.in_menus is not None: for i, l, t in settings.PAGE_MENU_TEMPLATES: if not str(i) in self.in_menus and t == template_name: return False return True def get_template_name(self): """ Subclasses can implement this to provide a template to use in ``mezzanine.pages.views.page``. """ return None
class Event(Displayable, RichText, AdminThumbMixin): parent = None # To make it compatible with the side_menu template children = DummyEmptyResultSet() start = models.DateTimeField() end = models.DateTimeField(blank=True, null=True) type = models.ForeignKey('calendar.EventType', blank=True, null=True) zip_import = models.FileField(verbose_name=_("Zip import"), blank=True, null=True, upload_to=upload_to("calendar.Event.zip_import", "events"), help_text=_("Upload a zip file containing images, and " "they'll be imported into this event.")) featured_image = FileField(verbose_name=_("Featured Image"), upload_to=upload_to("images", "images"), format="Image", max_length=255, null=True, blank=True) admin_thumb_field = "featured_image" featured_image_wide = FileField(verbose_name=_("Featured Image (Wide)"), upload_to=upload_to("images", "images"), format="Image", max_length=255, null=True, blank=True, help_text="For front-page slider images, please make sure they are at least 785px wide and 400px tall.") marquee_caption = models.CharField(null=True, blank=True, max_length=255) category = ForeignKey('sfpirgapp.Category', related_name='events') in_menus = MenusField("Show in menus", blank=True, null=True) location = models.TextField(blank=True, null=True) link_url = models.URLField('Registration URL', blank=True, null=True) @property def richtextpage(self): return self def in_menu_template(self, template_name): if self.in_menus is not None: for i, l, t in settings.PAGE_MENU_TEMPLATES: if not unicode(i) in self.in_menus and t == template_name: return False return True @models.permalink def get_absolute_url(self): return ("event", (), {"event": self.slug}) def save(self, delete_zip_import=True, *args, **kwargs): """ If a zip file is uploaded, extract any images from it and add them to the gallery, before removing the zip file. """ super(Event, self).save(*args, **kwargs) if self.zip_import: zip_file = ZipFile(self.zip_import) # import PIL in either of the two ways it can end up installed. try: from PIL import Image except ImportError: import Image for name in zip_file.namelist(): data = zip_file.read(name) try: image = Image.open(StringIO(data)) image.load() image = Image.open(StringIO(data)) image.verify() except: continue path = os.path.join(EVENTS_UPLOAD_DIR, self.slug, name.decode("utf-8")) try: saved_path = default_storage.save(path, ContentFile(data)) except UnicodeEncodeError: from warnings import warn warn("A file was saved that contains unicode " "characters in its path, but somehow the current " "locale does not support utf-8. You may need to set " "'LC_ALL' to a correct value, eg: 'en_US.UTF-8'.") path = os.path.join(EVENTS_UPLOAD_DIR, self.slug, unicode(name, errors="ignore")) saved_path = default_storage.save(path, ContentFile(data)) self.images.add(EventImage(file=saved_path)) if delete_zip_import: zip_file.close() self.zip_import.delete(save=True)
class Page(BasePage): """ A page in the page tree. This is the base class that custom content types need to subclass. """ parent = models.ForeignKey("Page", blank=True, null=True, related_name="children") in_menus = MenusField(_("Show in menus"), blank=True, null=True) titles = models.CharField(editable=False, max_length=1000, null=True) content_model = models.CharField(editable=False, max_length=50, null=True) login_required = models.BooleanField( _("Login required"), help_text=_("If checked, only logged in users can view this page")) class Meta: verbose_name = _("Page") verbose_name_plural = _("Pages") ordering = ("titles", ) order_with_respect_to = "parent" def __unicode__(self): return self.titles def get_absolute_url(self): """ URL for a page - for ``Link`` page types, simply return its slug since these don't have an actual URL pattern. Also handle the special case of the homepage being a page object. """ slug = self.slug if self.content_model == "link": # Ensure the URL is absolute. if not slug.lower().startswith("http"): slug = "/" + self.slug.lstrip("/") return slug if slug == "/": return reverse("home") else: return reverse("page", kwargs={"slug": slug}) def get_admin_url(self): return admin_url(self, "change", self.id) def save(self, *args, **kwargs): """ Create the titles field using the titles up the parent chain and set the initial value for ordering. """ if self.id is None: self.content_model = self._meta.object_name.lower() titles = [self.title] parent = self.parent while parent is not None: titles.insert(0, parent.title) parent = parent.parent self.titles = " / ".join(titles) super(Page, self).save(*args, **kwargs) def get_ascendants(self, for_user=None): """ Returns the ascendants for the page. Ascendants are cached in the ``_ascendants`` attribute, which is populated when the page is loaded via ``Page.objects.with_ascendants_for_slug``. """ if not self.parent_id: # No parents at all, bail out. return [] if not hasattr(self, "_ascendants"): # _ascendants has not been either page.get_ascendants or # Page.objects.assigned by with_ascendants_for_slug, so # run it to see if we can retrieve all parents in a single # query, which will occur if the slugs for each of the pages # have not been customised. if self.slug: args = (self.slug, for_user) pages = Page.objects.with_ascendants_for_slug(**args) self._ascendants = pages[0]._ascendants else: self._ascendants = [] if not self._ascendants: # Page has a parent but with_ascendants_for_slug failed to # find them due to custom slugs, so retrieve the parents # recursively. child = self while child.parent_id is not None: self._ascendants.append(child.parent) child = child.parent return self._ascendants @classmethod def get_content_models(cls): """ Return all Page subclasses. """ is_content_model = lambda m: m is not Page and issubclass(m, Page) return filter(is_content_model, models.get_models()) def get_content_model(self): """ Provies a generic method of retrieving the instance of the custom content type's model for this page. """ return getattr(self, self.content_model, None) def get_slug(self): """ Recursively build the slug from the chain of parents. """ slug = slugify(self.title) if self.parent is not None: return "%s/%s" % (self.parent.slug, slug) return slug def reset_slugs(self): """ Called when the parent page is changed in the admin and the slug plus all child slugs need to be recreated given the new parent. """ if not self.overridden(): self.slug = None self.save() for child in self.children.all(): child.reset_slugs() def overridden(self): """ Returns ``True`` if the page's slug has an explicitly defined urlpattern and is therefore considered to be overridden. """ from mezzanine.pages.views import page page_url = reverse("page", kwargs={"slug": self.slug}) resolved_view = resolve(page_url)[0] return resolved_view != page def can_add(self, request): """ Dynamic ``add`` permission for content types to override. """ return self.slug != "/" def can_change(self, request): """ Dynamic ``change`` permission for content types to override. """ return True def can_delete(self, request): """ Dynamic ``delete`` permission for content types to override. """ return True def set_helpers(self, context): """ Called from the ``page_menu`` template tag and assigns a handful of properties based on the current page, that are used within the various types of menus. """ current_page = context["_current_page"] current_page_id = getattr(current_page, "id", None) current_parent_id = getattr(current_page, "parent_id", None) # Am I a child of the current page? self.is_current_child = self.parent_id == current_page_id self.is_child = self.is_current_child # Backward compatibility # Is my parent the same as the current page's? self.is_current_sibling = self.parent_id == current_parent_id # Am I the current page? try: request = context["request"] except KeyError: # No request context, most likely when tests are run. self.is_current = False else: self.is_current = self.slug == path_to_slug(request.path_info) # Is the current page me or any page up the parent chain? def is_c_or_a(page_id): parent_id = context["_parent_page_ids"].get(page_id) return self.id == page_id or (parent_id and is_c_or_a(parent_id)) self.is_current_or_ascendant = lambda: bool(is_c_or_a(current_page_id)) # Am I a primary page? self.is_primary = self.parent_id is None # What's an ID I can use in HTML? self.html_id = self.slug.replace("/", "-") # Default branch level - gets assigned in the page_menu tag. self.branch_level = 0
class Project(Displayable, AdminThumbMixin): user = ForeignKey(User) liaison = ForeignKey( Liaison, blank=True, null=True, on_delete=models.SET_NULL, help_text='Who can SFPIRG contact with questions about this project?') time_per_week = RichTextField( blank=True, null=True, verbose_name= 'How much time per week can the Contact/Liaison devote to the student?' ) support_method = RichTextField( blank=True, null=True, verbose_name= 'How will the Contact/Liaison provide direction and support to the project?' ) logo = MyImageField( verbose_name="Project Image", upload_to=upload_to("sfpirgapp.project", "uploads/project-images"), format="Image", max_length=255, null=True, blank=True, help_text= 'Please upload an image to represent the project, or your logo. If you do not have one, do not worry, just leave this section blank.' ) admin_thumb_field = "logo" project_type = models.ManyToManyField( ProjectType, help_text='(Please select all that apply)') project_type_other = models.CharField( blank=True, null=True, max_length=255, verbose_name='Other Description', help_text= 'If you checked "other", please briefly describe your project type') project_subject = models.ManyToManyField( ProjectSubject, verbose_name='Project Issues', help_text='(Please select all that apply)') project_subject_other = models.CharField( blank=True, null=True, max_length=255, verbose_name='Other Issues', help_text= 'If you checked "other", please briefly describe your project subject') length = models.CharField( null=True, blank=True, max_length=255, verbose_name='Project Duration', help_text= ('(Please indicate how many months you expect this project to take; ' 'keeping in mind that if your project will take longer than one semester ' 'to complete the pool of students who can undertake it will be limited ' 'to grad students and students who undertake the project independently/not ' 'for course credit. Semesters run from Sept-Dec, Jan-Apr & May-Aug.)' )) description_long = RichTextField( blank=True, null=True, verbose_name='About this Project', help_text= '(What is the central research question you want answered or what project would you like help with? Please provide a detailed description of your project here.)' ) results_plan = RichTextField( blank=True, null=True, verbose_name='Use of Project Results', help_text= '(How do you plan to use the results of this project? For example, do you plan to publish it or will it be kept internal to your organization?)' ) larger_goal = RichTextField( blank=True, null=True, verbose_name='Deliverables', help_text= '(What do you want as specific deliverables for this project? For example, you might want a 10 page research paper on a topic, plus an executive summary, plus a power-point presentation to your organization\'s board of directors.)' ) researcher_qualities = RichTextField( blank=True, null=True, verbose_name='The Student Researcher Must Possess', help_text= '(What skills or attributes do you hope the student researcher will possess?)' ) date_created = models.DateTimeField(auto_now_add=True) date_start = models.DateField('Approval Date', blank=True, null=True) is_submitted = models.BooleanField(default=False) is_approved = models.BooleanField(default=False) is_underway = models.BooleanField(default=False) is_finished = models.BooleanField(default=False) is_completed_successfully = models.BooleanField(default=False) category = ForeignKey(Category, related_name='arx_projects') in_menus = MenusField("Show in menus", blank=True, null=True) admin_notes = models.TextField( blank=True, null=True, help_text='Internal Admin notes, not shown to the front-end users') search_fields = ('title', 'description_long', 'results_plan', 'larger_goal', 'researcher_qualities') def get_description(self): return self.description_long def set_description(self, value): pass description = property(get_description, set_description) @property def richtextpage(self): return self def in_menu_template(self, template_name): if self.in_menus is not None: for i, l, t in settings.PAGE_MENU_TEMPLATES: if not unicode(i) in self.in_menus and t == template_name: return False return True @property def organization_title(self): try: return self.user.profile.organization.title except: return '[None]' @property def featured_image(self): return self.logo @property def content(self): return self.description_long @models.permalink def get_absolute_url(self): return ('arx-project', (), {'slug': self.slug}) @models.permalink def get_apply_url(self): return ('arx-project-apply', (), {'slug': self.slug}) @property def formatted_project_subject(self): subjects = [subj.title for subj in self.project_subject.all()] if subjects == ['Other']: return self.project_subject_other return ', '.join(subjects) def save(self, *args, **kwargs): # Can't save a state that violates consistency if self.is_approved and not self.is_submitted: return if ((self.is_completed_successfully or self.is_finished or self.is_underway) and not (self.is_approved and self.is_submitted)): return if ((self.is_completed_successfully or self.is_finished) and not (self.is_approved and self.is_submitted and self.is_underway)): return if self.is_completed_successfully and not self.is_finished: return if self.is_approved: self.date_start = datetime.datetime.now() return super(Project, self).save(*args, **kwargs)
class ActionGroup(PageLike, Ownable): parent = None # To make it compatible with the side_menu template children = DummyEmptyResultSet( ) # To make it compatible with the side_menu template category = ForeignKey(Category, related_name='action_groups') announcements = RichTextField( null=True, blank=True, verbose_name='Announcements', help_text= 'Use this section to let people know about any upcoming events, volunteer opportunities, new initiatives - or just anything you want to draw attention to.' ) meetings = RichTextField( null=True, blank=True, verbose_name='Meetings', help_text= 'Let people know when & where you meet if you have regular meeting times. Don\'t forget you can book the SFPIRG lounge or meeting room to host your meetings.' ) contact_name = models.CharField('Main Contact Person', null=True, blank=True, max_length=255) contact_email = models.EmailField(null=True, blank=True, max_length=255, verbose_name='Contact Email') contact_phone = models.CharField(null=True, blank=True, max_length=255, verbose_name='Contact Telephone') group_email = models.EmailField(null=True, blank=True, max_length=255, verbose_name='Group Email') goals = RichTextField('Main Goal(s)', null=True, blank=True) timeline = RichTextField( 'Plans and Timeline', null=True, blank=True, help_text= 'Specific Plans and timeline for the semester (please be as concrete as possible)' ) oneliner = RichTextField('One-liner for SFPIRG promotional materials', null=True, blank=True) twoliner = RichTextField('One paragraph for SFPIRG website', null=True, blank=True) potential_members = RichTextField( 'Potential members of your group', null=True, blank=True, help_text= 'Please include the members of your potential Action Group: (NAME, PHONE, EMAIL)' ) links = RichTextField( null=True, blank=True, verbose_name='Links', help_text= 'Either to your website, or anywhere else you want to direct people to' ) facebook_url = models.URLField(null=True, blank=True, max_length=255) twitter = models.CharField(null=True, blank=True, max_length=255) google_plus_url = models.URLField(null=True, blank=True, max_length=255) mailing_list_url = models.URLField( null=True, blank=True, max_length=255, verbose_name='Link to Mailing List', help_text= 'You can create a free html email newsletter using mailchimp (www.mailchimp.com). Then people can automatically subscribe to your news. If you already have one, put in your Mailchimp List page address here. Visit mailchimp.com to get it quick' ) is_approved = models.BooleanField(default=False) in_menus = MenusField("Show in menus", blank=True, null=True) @property def richtextpage(self): return self def in_menu_template(self, template_name): if self.in_menus is not None: for i, _l, t in settings.PAGE_MENU_TEMPLATES: if not unicode(i) in self.in_menus and t == template_name: return False return True @models.permalink def get_absolute_url(self): return ('action-group', (), {'slug': self.slug}) def twitter_url(self): if not self.twitter: return '' if self.twitter.startswith('http://') or self.twitter.startswith( 'https://'): return self.twitter if self.twitter.startswith('@'): return 'http://twitter.com/%s' % self.twitter[1:] return 'http://twitter.com/%s' % self.twitter def save(self, *args, **kwargs): if self.is_approved: self.status = CONTENT_STATUS_PUBLISHED else: self.status = CONTENT_STATUS_DRAFT return super(ActionGroup, self).save(*args, **kwargs)