예제 #1
0
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
예제 #2
0
    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())
예제 #3
0
    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())
예제 #4
0
 class P3(BasePage):
     in_menus = MenusField(blank=True, null=True)
예제 #5
0
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
예제 #6
0
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)
예제 #7
0
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
예제 #8
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)
예제 #9
0
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)