Exemple #1
0
class Organisation(base.TimeStampedModel, TranslatableModel):
    name = models.CharField(max_length=512, unique=True)
    slug = models.SlugField(max_length=512, unique=True)

    translations = TranslatedFields(
        title=models.CharField(max_length=512),
        description_why=models.TextField(),
        description_how=models.TextField(),
        description=models.TextField(),
    )

    initiators = models.ManyToManyField(settings.AUTH_USER_MODEL)
    image = fields.ConfiguredImageField(
        'heroimage',
        upload_to='organisations/images',
        blank=True,
        verbose_name=_('Header image'),
        help_prefix=_(
            'The image sets the atmosphere for your organisation page.'),
    )
    logo = fields.ConfiguredImageField(
        'logo',
        upload_to='organisations/logos',
        blank=True,
        help_prefix=_('The official logo of your organisation.'),
    )
    twitter_handle = models.CharField(max_length=200, blank=True)
    facebook_handle = models.CharField(max_length=200, blank=True)
    instagram_handle = models.CharField(max_length=200, blank=True)
    webpage = models.URLField(blank=True)
    country = countries_fields.CountryField()
    place = models.CharField(max_length=200)

    objects = OrganisationManager()

    def __str__(self):
        return self.name

    def has_social_share(self):
        return (self.twitter_handle or self.facebook_handle
                or self.instagram_handle or self.webpage)

    def has_initiator(self, user):
        return user in self.initiators.all()

    def get_absolute_url(self):
        from django.core.urlresolvers import reverse
        return reverse('organisation-detail', args=[str(self.slug)])
Exemple #2
0
class Organisation(models.Model):
    slug = AutoSlugField(populate_from='name', unique=True)
    name = models.CharField(max_length=512)
    initiators = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        blank=True,
    )
    groups = models.ManyToManyField(Group, blank=True)
    logo = fields.ConfiguredImageField(
        'logo',
        verbose_name=_('Logo'),
        help_prefix=_(
            'The image will be shown in the newsletter in the banner.'),
        upload_to='organisation/logos',
        blank=True)
    address = models.TextField(blank=True, verbose_name=_('Postal address'))
    url = models.URLField(blank=True,
                          verbose_name=_('Website of organisation'))

    def __str__(self):
        return self.name

    def has_initiator(self, user):
        return user in self.initiators.all()

    def get_absolute_url(self):
        return reverse('plan-list')
Exemple #3
0
class Idea(module_models.Item):
    slug = AutoSlugField(populate_from='name', unique=True)
    name = models.CharField(max_length=120)
    description = RichTextField()
    image = fields.ConfiguredImageField(
        'idea_image',
        upload_to='ideas/images',
        blank=True,
    )
    ratings = GenericRelation(rating_models.Rating,
                              related_query_name='idea',
                              object_id_field='object_pk')
    comments = GenericRelation(comment_models.Comment,
                               related_query_name='idea',
                               object_id_field='object_pk')
    category = CategoryField()

    objects = IdeaQuerySet.as_manager()

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.description = transforms.clean_html_field(self.description)
        super(Idea, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('idea-detail', args=[str(self.slug)])
Exemple #4
0
class IdeaSection(models.Model):
    title = models.CharField(max_length=50,
                             verbose_name=_('Idea title'),
                             help_text=TITLE_HELP)
    subtitle = models.CharField(max_length=200,
                                verbose_name=_('Idea subtitle'),
                                help_text=SUBTITLE_HELP,
                                blank=True)
    pitch = models.TextField(max_length=500,
                             verbose_name=_('Idea pitch'),
                             help_text=PITCH_HELP)
    image = fields.ConfiguredImageField(
        'image',
        upload_to='ideas/images',
        verbose_name=_('Visualize your idea'),
        help_text=IMAGE_HELP,
    )
    country_of_implementation = CountryField(
        countries=EuropeanCountries,
        help_text=COUNTRY_OF_IMPLEMENTATION_HELP,
        multiple=True)
    field_of_action = MultiSelectField(max_length=255,
                                       choices=FIELD_OF_ACTION_CHOICES,
                                       max_choices=2,
                                       help_text=FIELD_OF_ACTION_HELP)
    field_of_action_other = models.CharField(
        max_length=50,
        blank=True,
        verbose_name=_('If you chose “Other” please specify briefly here'),
        help_text=_('(Max. 50 characters.)'))

    class Meta:
        abstract = True

    @property
    def idea_field_of_action_names(self):
        choices = dict(FIELD_OF_ACTION_CHOICES)
        return [
            choices[action] for action in self.field_of_action
            if action != 'OT'
        ]

    @property
    def all_idea_field_of_action_names(self):
        idea_field_of_actions = self.idea_field_of_action_names
        if self.field_of_action_other:
            idea_field_of_actions.append(self.field_of_action_other)
        return idea_field_of_actions
Exemple #5
0
class MapIdea(module_models.Item):
    slug = AutoSlugField(populate_from='name', unique=True)
    name = models.CharField(max_length=120)
    description = RichTextField()
    image = fields.ConfiguredImageField(
        'idea_image',
        upload_to='ideas/images',
        blank=True,
    )
    ratings = GenericRelation(rating_models.Rating,
                              related_query_name='idea',
                              object_id_field='object_pk')
    comments = GenericRelation(comment_models.Comment,
                               related_query_name='idea',
                               object_id_field='object_pk')
    category = CategoryField()

    point = map_fields.PointField(
        verbose_name=_('Where can your idea be located on a map?'),
        help_text=_('Click inside the marked area '
                    'or type in an address to set the marker. A set '
                    'marker can be dragged when pressed.'))

    point_label = models.CharField(
        blank=True,
        default='',
        max_length=255,
        verbose_name=_('Label of the ideas location'),
        help_text=_('This could be an address or the name of a landmark.'),
    )

    objects = IdeaQuerySet.as_manager()

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.description = transforms.clean_html_field(self.description)
        super().save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('mapidea-detail', args=[str(self.slug)])
Exemple #6
0
class Organisation(models.Model):
    slug = AutoSlugField(populate_from='name', unique=True)
    name = models.CharField(max_length=512)
    initiators = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
    title = models.CharField(
        verbose_name=_('Title of your organisation'),
        max_length=100,
        default='Organisation',
        help_text=_('The title of your organisation will be shown '
                    'on the landing page. max. 100 characters'))
    description = models.CharField(
        max_length=400,
        verbose_name=_('Short description of your organisation'),
        help_text=_('The description will be displayed on the '
                    'landing page. max. 400 characters'))
    logo = images_fields.ConfiguredImageField(
        'logo',
        verbose_name=_('Logo'),
        help_text=_('The Logo representing your organisation.'
                    ' The image must be square and it '
                    'should be min. 200 pixels wide and 200 '
                    'pixels tall. Allowed file formats are '
                    'png, jpeg, gif. The file size '
                    'should be max. 5 MB.'),
        upload_to='organisations/logos',
        blank=True)
    slogan = models.CharField(max_length=200,
                              verbose_name=_('Slogan'),
                              blank=True,
                              help_text=_('The slogan will be shown below '
                                          'the title of your organisation on '
                                          'the landing page. The slogan can '
                                          'provide context or additional '
                                          'information to the title. '
                                          'max. 200 characters'))
    image = images_fields.ConfiguredImageField(
        'heroimage',
        verbose_name=_('Header image'),
        help_prefix=_(
            'The image will be shown as a decorative background image.'),
        upload_to='organisations/backgrounds',
        blank=True)
    image_copyright = models.CharField(
        max_length=200,
        verbose_name=_('Header image copyright'),
        blank=True,
        help_text=_('Author, which is displayed in the header image.'))
    information = RichTextUploadingField(
        config_name='image-editor',
        verbose_name=_('Information about your organisation'),
        help_text=_('You can provide general information about your '
                    'participation platform to your visitors. '
                    'It’s also helpful to name a general person '
                    'of contact for inquiries. The information '
                    'will be shown on a separate page that '
                    'can be reached via the main menu.'),
        blank=True)
    imprint = RichTextField(
        verbose_name=_('Imprint'),
        help_text=_('Please provide all the legally '
                    'required information of your imprint. '
                    'The imprint will be shown on a separate page.'))
    is_supporting = models.BooleanField(
        default=False,
        verbose_name=_('is a supporting organisation'),
        help_text=_('For supporting organisations, the banner asking '
                    'for donations is not displayed on their pages.'))

    def __str__(self):
        return self.name

    def has_initiator(self, user):
        return (self.initiators.filter(id=user.id).exists())

    def get_absolute_url(self):
        return '/{}'.format(self.name).lower()

    def save(self, *args, **kwargs):
        self.information = transforms.clean_html_field(self.information,
                                                       'image-editor')
        self.imprint = transforms.clean_html_field(self.imprint)
        super().save(*args, **kwargs)
Exemple #7
0
class Project(ProjectContactDetailMixin,
              ProjectLocationMixin,
              base.TimeStampedModel):
    slug = AutoSlugField(populate_from='name', unique=True)
    name = models.CharField(
        max_length=120,
        verbose_name=_('Title of your project'),
        help_text=_('This title will appear on the '
                    'teaser card and on top of the project '
                    'detail page. It should be max. 120 characters long')
    )
    organisation = models.ForeignKey(
        settings.A4_ORGANISATIONS_MODEL,
        on_delete=models.CASCADE)

    group = models.ForeignKey(
        Group,
        on_delete=models.SET_NULL,
        blank=True,
        null=True)

    description = models.CharField(
        max_length=250,
        verbose_name=_('Short description of your project'),
        help_text=_('This short description will appear on '
                    'the header of the project and in the teaser. '
                    'It should briefly state the goal of the project '
                    'in max. 250 chars.')
    )
    information = RichTextCollapsibleUploadingField(
        blank=True,
        config_name='collapsible-image-editor',
        verbose_name=_('Description of your project'),
        help_text=_('This description should tell participants '
                    'what the goal of the project is, how the project’s '
                    'participation will look like. It will be always visible '
                    'in the „Info“ tab on your project’s page.')
    )
    result = RichTextUploadingField(
        blank=True,
        config_name='image-editor',
        verbose_name=_('Results of your project'),
        help_text=_('Here you should explain what the expected outcome of the '
                    'project will be and how you are planning to use the '
                    'results. If the project is finished you should add a '
                    'summary of the results.')
    )
    is_public = models.BooleanField(
        default=True,
        verbose_name=_('Access to the project'),
        help_text=_('Please indicate whether this project should be public '
                    'or restricted to invited users. Teasers for your project '
                    'including title and short description will always be '
                    'visible to everyone')
    )
    is_draft = models.BooleanField(default=True)
    image = fields.ConfiguredImageField(
        'heroimage',
        verbose_name=_('Header image'),
        help_prefix=_(
            'The image will be shown as a decorative background image.'
        ),
        upload_to='projects/backgrounds',
        blank=True)
    image_copyright = fields.ImageCopyrightField(image_name=_('Header image'))
    tile_image = fields.ConfiguredImageField(
        'tileimage',
        verbose_name=_('Tile image'),
        help_prefix=_(
            'The image will be shown in the project tile.'
        ),
        upload_to='projects/tiles',
        blank=True)
    tile_image_copyright = fields.ImageCopyrightField(
        image_name=_('Tile image'))
    participants = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='project_participant',
        blank=True,
    )
    moderators = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='project_moderator',
        blank=True,
    )
    is_archived = models.BooleanField(
        default=False,
        verbose_name=_('Project is archived'),
        help_text=_('Exclude this project from all listings by default. '
                    'You can still access this project by using filters.'),
    )
    topics = TopicField(
        verbose_name=_('Project topics'),
        help_text=_('Add topics to your project.')
    )

    objects = ProjectManager()

    class Meta:
        ordering = ['-created']

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.information = html_transforms.clean_html_field(
            self.information, 'collapsible-image-editor')
        self.result = html_transforms.clean_html_field(
            self.result, 'image-editor')
        super(Project, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('project-detail', kwargs=dict(slug=self.slug))

    def has_member(self, user):
        """
        Everybody is member of all public projects and private projects can
        be joined as moderator or participant.
        """
        return (
            (user.is_authenticated and self.is_public)
            or (user in self.participants.all())
            or (user in self.moderators.all())
        )

    def is_group_member(self, user):
        if self.group:
            return user.groups.filter(id=self.group.id).exists()
        return False

    def has_moderator(self, user):
        return user in self.moderators.all()

    @cached_property
    def topic_names(self):
        if hasattr(settings, 'A4_PROJECT_TOPICS'):
            choices = dict(settings.A4_PROJECT_TOPICS)
            return [choices[topic] for topic in self.topics]
        return []

    @cached_property
    def other_projects(self):
        other_projects = self.organisation.project_set\
            .filter(is_draft=False, is_archived=False).exclude(slug=self.slug)
        return other_projects

    @cached_property
    def is_private(self):
        return not self.is_public

    @cached_property
    def last_active_phase(self):
        """
        Return the last active phase.

        The last active phase is defined as the phase that out of all past
        and currently active phases started last.
        This property is used to determine which phase view is shown.
        """
        return self.phases\
            .past_and_active_phases()\
            .last()

    @cached_property
    def last_active_module(self):
        """
        Return the module of the last active phase.

        Attention: Might be _deprecated_ and replaced by logic coming from
        the modules.
        """
        last_active_phase = self.last_active_phase
        if last_active_phase:
            return last_active_phase.module
        return None

    @cached_property
    def active_phase(self):
        """
        Return the currently active phase.

        The currently active phase is defined as the phase that out of all
        currently active phases started last. This is analogous to the last
        active phase.

        Attention: This method is _deprecated_ as multiple phases (in
        different modules) may be active at the same time.
        """
        warnings.warn(
            "active_phase is deprecated; "
            "use active_phase_ends_next or active_module_ends_next",
            DeprecationWarning
        )
        last_active_phase = self.last_active_phase
        if last_active_phase and not last_active_phase.is_over:
            return last_active_phase
        return None

    @cached_property
    def active_phase_ends_next(self):
        """
        Return the currently active phase that ends next.
        """
        return self.phases.active_phases().order_by('end_date').first()

    @cached_property
    def days_left(self):
        """
        Return the number of days left in the currently active phase.

        Attention: This method is _deprecated_ as multiple phases may be
        active at the same time.
        """
        warnings.warn(
            "days_left is deprecated as it relies on active_phase; "
            "use module_running_days_left",
            DeprecationWarning
        )
        active_phase = self.active_phase
        if active_phase:
            today = timezone.now().replace(hour=0, minute=0, second=0)
            time_delta = active_phase.end_date - today
            return time_delta.days
        return None

    @cached_property
    def time_left(self):
        """
        Return the time left in the currently active phase that ends next.

        Attention: _deprecated_ as in the projects logic from the modules
        should be used.
        """
        warnings.warn(
            "time_left is deprecated as in the projects "
            "logic from the modules should be used; "
            "use module_running_time_left",
            DeprecationWarning
        )

        def seconds_in_units(seconds):
            unit_totals = []

            unit_limits = [
                           ([_('day'), _('days')], 24 * 3600),
                           ([_('hour'), _('hours')], 3600),
                           ([_('minute'), _('minutes')], 60)
                          ]

            for unit_name, limit in unit_limits:
                if seconds >= limit:
                    amount = int(float(seconds) / limit)
                    if amount > 1:
                        unit_totals.append((unit_name[1], amount))
                    else:
                        unit_totals.append((unit_name[0], amount))
                    seconds = seconds - (amount * limit)

            return unit_totals

        active_phase = self.active_phase_ends_next
        if active_phase:
            today = timezone.now()
            time_delta = active_phase.end_date - today
            seconds = time_delta.total_seconds()
            time_delta_list = seconds_in_units(seconds)
            best_unit = time_delta_list[0]
            time_delta_str = '{} {}'.format(str(best_unit[1]),
                                            str(best_unit[0]))
            return time_delta_str

    @cached_property
    def active_phase_progress(self):
        """
        Return the progress of the currently active phase that ends next
        in percent.

        Attention: _deprecated_ as in the projects logic from the modules
        should be used.
        """
        warnings.warn(
            "active_phase_progress is deprecated as in the projects "
            "logic from the modules should be used; "
            "use module_running_progress",
            DeprecationWarning
        )
        active_phase = self.active_phase_ends_next
        if active_phase:
            time_gone = timezone.now() - active_phase.start_date
            total_time = active_phase.end_date - active_phase.start_date
            return round(time_gone / total_time * 100)
        return None

    @cached_property
    def phases(self):
        from adhocracy4.phases import models as phase_models
        return phase_models.Phase.objects.filter(module__project=self)

    @cached_property
    def future_phases(self):
        return self.phases.future_phases()

    @cached_property
    def past_phases(self):
        return self.phases.past_phases()

    @cached_property
    def has_started(self):
        return self.phases.past_and_active_phases().exists()

    @cached_property
    def end_date(self):
        last_phase = self.phases.exclude(end_date=None)\
            .order_by('end_date').last()
        end_date = last_phase.end_date
        if self.events:
            last_event = self.events.order_by('date').last()
            if last_event.date > end_date:
                end_date = last_event.date
        return end_date

    @cached_property
    def events(self):
        if hasattr(self, 'offlineevent_set'):
            return self.offlineevent_set.all()

    @cached_property
    def has_future_events(self):
        if self.events:
            now = timezone.now()
            return self.events.filter(date__gt=now).exists()
        return False

    @cached_property
    def has_finished(self):
        return not self.phases.active_phases().exists()\
               and not self.phases.future_phases().exists()\
               and not self.has_future_events

    @cached_property
    def modules(self):
        return self.module_set.all()

    @cached_property
    def running_modules(self):
        return self.modules.running_modules()

    @cached_property
    def running_module_ends_next(self):
        """
        Return the currently active module that ends next.
        """
        return self.running_modules.order_by('module_end').first()

    @cached_property
    def past_modules(self):
        """Return past modules ordered by start."""
        return self.modules.past_modules()

    @cached_property
    def future_modules(self):
        """
        Return future modules ordered by start date.

        Note: Modules without a start date are assumed to start in the future.
        """
        return self.modules.future_modules()

    @cached_property
    def module_running_days_left(self):
        """
        Return the number of days left in the currently running module that
        ends next.

        Attention: It's a bit coarse and should only be used for estimations
        like 'ending soon', but NOT to display the number of days a project
        is still running. For that use module_running_time_left.
        """
        running_module = self.running_module_ends_next
        if running_module:
            today = timezone.now().replace(hour=0, minute=0, second=0)
            time_delta = running_module.module_end - today
            return time_delta.days
        return None

    @cached_property
    def module_running_time_left(self):
        """
        Return the time left in the currently running module that ends next.
        """

        running_module = self.running_module_ends_next
        if running_module:
            return running_module.module_running_time_left
        return None

    @cached_property
    def module_running_progress(self):
        """
        Return the progress of the currently running module that ends next
        in percent.
        """
        running_module = self.running_module_ends_next
        if running_module:
            return running_module.module_running_progress
        return None

    @cached_property
    def is_archivable(self):
        return not self.is_archived and self.has_finished
Exemple #8
0
class User(auth_models.AbstractBaseUser, auth_models.PermissionsMixin):
    username = models.CharField(_('username'),
                                max_length=60,
                                unique=True,
                                help_text=USERNAME_HELP,
                                validators=[USERNAME_VALIDATOR],
                                error_messages={
                                    'unique': _(USERNAME_NOT_UNIQUE),
                                })

    email = models.EmailField(_('email address'),
                              unique=True,
                              error_messages={
                                  'unique': EMAIL_NOT_UNIQUE,
                              })
    is_staff = models.BooleanField(_('staff status'),
                                   default=False,
                                   help_text=IS_STAFF_HELP)
    is_active = models.BooleanField(_('active'),
                                    default=True,
                                    help_text=IS_ACTIVE_HELP)
    date_joined = models.DateTimeField(editable=False, default=timezone.now)

    get_notifications = models.BooleanField(
        verbose_name=_('Send me email notifications'),
        default=True,
        help_text=GET_NOTIFICATIONS_HELP)
    _avatar = fields.ConfiguredImageField(
        'avatar',
        upload_to='users/images',
        blank=True,
        verbose_name=_('Avatar picture'),
    )

    description = models.CharField(
        blank=True,
        max_length=250,
        verbose_name=_('Short description about yourself'),
        help_text=_('Write a little bit about yourself. '
                    '(max. 250 characters)'))

    twitter_handle = models.CharField(
        blank=True,
        max_length=15,
        verbose_name=_('Twitter name'),
    )

    facebook_handle = models.CharField(
        blank=True,
        max_length=50,
        verbose_name=_('Facebook name'),
    )

    instagram_handle = models.CharField(
        blank=True,
        max_length=30,
        verbose_name=_('Instagram name'),
    )

    country = countries_fields.CountryField(
        blank=True,
        verbose_name=_('Country of residence'),
    )

    city = models.CharField(
        blank=True,
        max_length=80,
        verbose_name=_('City of residence'),
    )

    birthdate = models.DateField(
        blank=True,
        null=True,
        verbose_name=_('Date of birth'),
    )

    gender = models.CharField(
        blank=True,
        verbose_name=_('Gender'),
        max_length=2,
        choices=[
            ('M', _('Male')),
            ('F', _('Female')),
            ('T', _('Transgender')),
            ('TF', _('Transgender Female')),
            ('TM', _('Transgender Male')),
            ('I', _('Intersex')),
            ('GF', _('Gender Fluid')),
            ('O', _('Other')),
        ],
    )

    languages = models.CharField(
        blank=True,
        verbose_name=_('Languages'),
        max_length=150,
        help_text=_('Enter the languages you’re speaking.'))

    timezone = models.CharField(blank=True,
                                verbose_name=_('Time zone'),
                                max_length=100,
                                choices=[(t, t) for t in common_timezones])

    objects = auth_models.UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

    class Meta:
        verbose_name = _("User")
        verbose_name_plural = _("Users")

    def get_absolute_url(self):
        from django.core.urlresolvers import reverse
        return reverse('profile', kwargs={'slug': str(self.username)})

    def __str__(self):
        return self.get_full_name()

    @property
    def has_social_share(self):
        return (self.twitter_handle or self.facebook_handle
                or self.instagram_handle)

    @property
    def organisations(self):
        return self.organisation_set.all()

    @property
    def avatar(self):
        if self._avatar:
            return self._avatar

    @property
    def default_avatar(self):
        id = self.pk % 6
        return static('images/penguin_{}.png'.format(id))

    @property
    def age(self):
        today = date.today()
        years_difference = today.year - self.birthdate.year
        is_before_birthday = (today.month, today.day) < (self.birthdate.month,
                                                         self.birthdate.day)
        elapsed_years = years_difference - int(is_before_birthday)
        return elapsed_years

    def get_full_name(self):
        """
        Returns the first_name plus the last_name, with a space in between.
        """
        full_name = '%s <%s>' % (self.username, self.email)
        return full_name.strip()

    def get_short_name(self):
        "Returns the short name for the user."
        return self.username

    def signup(self, username, email, timezone, commit=True):
        """Update the fields required for sign-up."""
        self.username = username
        self.email = email
        self.timezone = timezone
        if commit:
            self.save()
Exemple #9
0
class Organisation(models.Model):
    slug = AutoSlugField(populate_from='name', unique=True)
    name = models.CharField(max_length=512)
    initiators = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
    title = models.CharField(
        verbose_name=_('Title of your organisation'),
        max_length=100,
        help_text=_('The title of your organisation will be shown '
                    'on the landing page. max. 100 characters'),
        blank=True)
    description = models.CharField(
        max_length=800,
        verbose_name=_('Short description of your organisation'),
        help_text=_('The description will be displayed on the '
                    'landing page. max. 800 characters'),
        blank=True)
    logo = images_fields.ConfiguredImageField(
        'logo',
        verbose_name=_('Logo'),
        help_text=_('The Logo representing your organisation.'
                    ' The image must be square and it '
                    'should be min. 200 pixels wide and 200 '
                    'pixels tall. Allowed file formats are '
                    'png, jpeg, gif. The file size '
                    'should be max. 5 MB.'),
        upload_to='organisations/logos',
        blank=True)
    slogan = models.CharField(max_length=200,
                              verbose_name=_('Slogan'),
                              blank=True,
                              help_text=_('The slogan will be shown below '
                                          'the title of your organisation on '
                                          'the landing page. The slogan can '
                                          'provide context or additional '
                                          'information to the title. '
                                          'max. 200 characters'))
    url = models.URLField(blank=True,
                          verbose_name='Organisation website',
                          help_text=_('Please enter '
                                      'a full url which '
                                      'starts with https:// '
                                      'or http://'))
    image = images_fields.ConfiguredImageField(
        'heroimage',
        verbose_name=_('Header image'),
        help_prefix=_(
            'The image will be shown as a decorative background image.'),
        upload_to='organisations/backgrounds',
        blank=True)
    image_copyright = models.CharField(
        max_length=200,
        verbose_name=_('Header image copyright'),
        blank=True,
        help_text=_('Author, which is displayed in the header image.'))
    information = RichTextCollapsibleUploadingField(
        config_name='collapsible-image-editor',
        verbose_name=_('Information about your organisation'),
        help_text=_('You can provide general information about your '
                    'participation platform to your visitors. '
                    'It’s also helpful to name a general person '
                    'of contact for inquiries. The information '
                    'will be shown on a separate "About" page that '
                    'can be reached via the main menu.'),
        blank=True)
    twitter_handle = models.CharField(
        max_length=100,
        blank=True,
        verbose_name='Twitter handle',
    )
    facebook_handle = models.CharField(
        max_length=100,
        blank=True,
        verbose_name='Facebook handle',
    )
    instagram_handle = models.CharField(
        max_length=100,
        blank=True,
        verbose_name='Instagram handle',
    )
    imprint = RichTextField(
        verbose_name=_('Imprint'),
        help_text=_('Please provide all the legally '
                    'required information of your imprint. '
                    'The imprint will be shown on a separate page.'),
        blank=True)
    terms_of_use = RichTextField(
        verbose_name=_('Terms of use'),
        help_text=_('Please provide all the legally '
                    'required information of your terms of use. '
                    'The terms of use will be shown on a separate page.'),
        blank=True)
    data_protection = RichTextField(
        verbose_name=_('Data protection policy'),
        help_text=_('Please provide all the legally '
                    'required information of your data protection. '
                    'The data protection policy will be shown on a '
                    'separate page.'),
        blank=True)
    netiquette = RichTextField(
        verbose_name=_('Netiquette'),
        help_text=_('Please provide a netiquette for the participants. '
                    'The netiquette helps improving the climate of '
                    'online discussions and supports the moderation.'),
        blank=True)
    is_supporting = models.BooleanField(
        default=False,
        verbose_name=_('is a supporting organisation'),
        help_text=_('For supporting organisations, the banner asking '
                    'for donations is not displayed on their pages.'))
    site = models.ForeignKey(Site,
                             on_delete=models.SET_NULL,
                             blank=True,
                             null=True)

    def __str__(self):
        return self.name

    @cached_property
    def projects(self):
        return Project.objects \
            .filter(organisation=self,
                    is_archived=False,
                    is_draft=False)

    def get_projects_list(self, user):
        projects = query.filter_viewable(self.projects, user)
        now = timezone.now()

        sorted_active_projects = projects\
            .annotate(project_start=models.Min('module__phase__start_date'))\
            .annotate(project_end=models.Max('module__phase__end_date'))\
            .filter(project_start__lte=now, project_end__gt=now)\
            .order_by('project_end')

        sorted_future_projects = projects\
            .annotate(project_start=models.Min('module__phase__start_date'))\
            .filter(models.Q(project_start__gt=now)
                    | models.Q(project_start=None))\
            .order_by('project_start')

        sorted_past_projects = projects\
            .annotate(project_start=models.Min('module__phase__start_date'))\
            .annotate(project_end=models.Max('module__phase__end_date'))\
            .filter(project_end__lt=now)\
            .order_by('project_start')

        return sorted_active_projects, \
            sorted_future_projects, \
            sorted_past_projects

    def has_initiator(self, user):
        return (self.initiators.filter(id=user.id).exists())

    def has_org_member(self, user):
        return (Member.objects.filter(member__id=user.id,
                                      organisation__id=self.id).exists())

    def get_absolute_url(self):
        return reverse('organisation', kwargs={'organisation_slug': self.slug})

    def has_social_share(self):
        return (self.twitter_handle or self.facebook_handle
                or self.instagram_handle)

    def save(self, *args, **kwargs):
        self.information = transforms.clean_html_field(
            self.information, 'collapsible-image-editor')
        self.imprint = transforms.clean_html_field(self.imprint)
        super().save(*args, **kwargs)
Exemple #10
0
class User(auth_models.AbstractBaseUser, auth_models.PermissionsMixin):
    username = models.CharField(
        _('username'),
        max_length=75,
        unique=True,
        help_text=_(
            'Required. 60 characters or less. Letters, digits, spaces and '
            '@/./+/-/_ only.'),
        validators=[
            validators.RegexValidator(USERNAME_REGEX, USERNAME_INVALID_MESSAGE,
                                      'invalid')
        ],
        error_messages={
            'unique': _('A user with that username already exists.')
        })

    email = models.EmailField(
        _('email address'),
        unique=True,
        error_messages={
            'unique': _('A user with that email address already exists.')
        })

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into this admin site.'))

    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'))

    _avatar = fields.ConfiguredImageField(
        'avatar',
        upload_to='users/images',
        blank=True,
        verbose_name=_('Profile picture'),
        help_text=_('Please upload a picture in landscape format, at least '
                    '340x340 px. You can upload a .jpg, .png or .gif.'))

    date_joined = models.DateTimeField(editable=False, default=timezone.now)

    twitter_handle = models.CharField(
        blank=True,
        max_length=15,
        verbose_name=_('Twitter name'),
    )

    facebook_handle = models.CharField(
        blank=True,
        max_length=50,
        verbose_name=_('Facebook name'),
    )

    instagram_handle = models.CharField(
        blank=True,
        max_length=30,
        verbose_name=_('Instagram name'),
    )

    website = models.URLField(blank=True,
                              verbose_name=_('Website'),
                              help_text=_(
                                  'Please add either https:// or http:// '
                                  'at the beginning of your URL.'))

    get_notifications = models.BooleanField(
        verbose_name=_("my own ideas and ideas that I'm watching"),
        default=True)

    objects = auth_models.UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

    @property
    def avatar(self):
        if self._avatar:
            return self._avatar

    @property
    def fallback_avatar(self):
        number = self.pk % 3
        return static('images/avatar-{0:02d}.svg'.format(number))

    @property
    def is_innovator(self):
        return Idea.objects.filter(Q(creator=self)
                                   | Q(co_workers=self)).count() > 0

    def avatar_or_fallback_url(self):
        if self._avatar:
            return get_thumbnailer(self._avatar)['avatar'].url
        else:
            return self.fallback_avatar

    def get_short_name(self):
        return self.username

    def get_full_name(self):
        full_name = '%s <%s>' % (self.username, self.email)
        return full_name.strip()

    def signup(self, username, email, commit=True):
        """Update the fields required for sign-up."""
        self.username = username
        self.email = email
        if commit:
            self.save()

    def get_absolute_url(self):
        return reverse('profile', args=[str(self.username)])
Exemple #11
0
class Project(base.TimeStampedModel):
    slug = AutoSlugField(populate_from='name', unique=True)
    name = models.CharField(
        max_length=120,
        verbose_name=_('Title of your project'),
        help_text=_('This title will appear on the '
                    'teaser card and on top of the project '
                    'detail page. It should be max. 120 characters long'))
    organisation = models.ForeignKey(settings.A4_ORGANISATIONS_MODEL,
                                     on_delete=models.CASCADE)
    description = models.CharField(
        max_length=250,
        verbose_name=_('Short description of your project'),
        help_text=_('This short description will appear on '
                    'the header of the project and in the teaser. '
                    'It should briefly state the goal of the project '
                    'in max. 250 chars.'))
    information = RichTextCollapsibleUploadingField(
        blank=True,
        config_name='collapsible-image-editor',
        verbose_name=_('Description of your project'),
        help_text=_('This description should tell participants '
                    'what the goal of the project is, how the project’s '
                    'participation will look like. It will be always visible '
                    'in the „Info“ tab on your project’s page.'))
    result = RichTextUploadingField(
        blank=True,
        config_name='image-editor',
        verbose_name=_('Results of your project'),
        help_text=_('Here you should explain what the expected outcome of the '
                    'project will be and how you are planning to use the '
                    'results. If the project is finished you should add a '
                    'summary of the results.'))
    is_public = models.BooleanField(
        default=True,
        verbose_name=_('Access to the project'),
        help_text=_('Please indicate whether this project should be public '
                    'or restricted to invited users. Teasers for your project '
                    'including title and short description will always be '
                    'visible to everyone'))
    is_draft = models.BooleanField(default=True)
    image = fields.ConfiguredImageField(
        'heroimage',
        verbose_name=_('Header image'),
        help_prefix=_(
            'The image will be shown as a decorative background image.'),
        upload_to='projects/backgrounds',
        blank=True)
    image_copyright = fields.ImageCopyrightField(image_name=_('Header image'))
    tile_image = fields.ConfiguredImageField(
        'tileimage',
        verbose_name=_('Tile image'),
        help_prefix=_('The image will be shown in the project tile.'),
        upload_to='projects/tiles',
        blank=True)
    tile_image_copyright = fields.ImageCopyrightField(
        image_name=_('Tile image'))
    participants = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='project_participant',
        blank=True,
    )
    moderators = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='project_moderator',
        blank=True,
    )
    is_archived = models.BooleanField(
        default=False,
        verbose_name=_('Project is archived'),
        help_text=_('Exclude this project from all listings by default. '
                    'You can still access this project by using filters.'),
    )

    objects = ProjectManager()

    class Meta:
        ordering = ['-created']

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.information = html_transforms.clean_html_field(
            self.information, 'collapsible-image-editor')
        self.result = html_transforms.clean_html_field(self.result,
                                                       'image-editor')
        super(Project, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('project-detail', kwargs=dict(slug=self.slug))

    def has_member(self, user):
        """
        Everybody is member of all public projects and private projects can
        be joined as moderator or participant.
        """
        return ((user.is_authenticated() and self.is_public)
                or (user in self.participants.all())
                or (user in self.moderators.all()))

    def has_moderator(self, user):
        return user in self.moderators.all()

    @property
    def other_projects(self):
        other_projects = self.organisation.project_set\
            .filter(is_draft=False, is_archived=False).exclude(slug=self.slug)
        return other_projects

    @property
    def is_private(self):
        return not self.is_public

    @property
    def modules(self):
        return self.module_set.all()

    @property
    def last_active_phase(self):
        """
        Return the last active phase.

        The last active phase is defined as the phase that out of all past
        and currently active phases started last.
        """
        return self.phases\
            .past_and_active_phases()\
            .last()

    @property
    def last_active_module(self):
        """Return the module of the last active phase."""
        last_active_phase = self.last_active_phase
        if last_active_phase:
            return last_active_phase.module
        return None

    @property
    def active_phase(self):
        """
        Return the currently active phase.

        The currently active phase is defined as the phase that out of all
        currently active phases started last. This is analogous to the last
        active phase.

        Attention: This method is _deprecated_ as multiple phases may be
        active at the same time.
        """
        last_active_phase = self.last_active_phase
        if last_active_phase and not last_active_phase.is_over:
            return last_active_phase
        return None

    @property
    def days_left(self):
        """
        Return the number of days left in the currently active phase.

        Attention: This method is _deprecated_ as multiple phases may be
        active at the same time.
        """
        active_phase = self.active_phase
        if active_phase:
            today = timezone.now().replace(hour=0, minute=0, second=0)
            time_delta = active_phase.end_date - today
            return time_delta.days
        return None

    @property
    def phases(self):
        from adhocracy4.phases import models as phase_models
        return phase_models.Phase.objects.filter(module__project=self)

    @property
    def future_phases(self):
        return self.phases.future_phases()

    @property
    def past_phases(self):
        return self.phases.past_phases()

    @property
    def has_started(self):
        return self.phases.past_and_active_phases().exists()

    @property
    def has_finished(self):
        return not self.phases.active_phases().exists()\
               and not self.phases.future_phases().exists()

    @property
    def is_archivable(self):
        return not self.is_archived and self.has_finished