示例#1
0
class SitePlatformSettings(TranslatableModel, BasePlatformSettings):
    contact_email = models.EmailField(null=True, blank=True)
    contact_phone = models.CharField(max_length=100, null=True, blank=True)
    copyright = models.CharField(max_length=100, null=True, blank=True)
    powered_by_text = models.CharField(max_length=100, null=True, blank=True)
    powered_by_link = models.CharField(max_length=100, null=True, blank=True)
    powered_by_logo = models.ImageField(
        null=True, blank=True, upload_to='site_content/',
        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES,
            ),
            validate_file_infection
        ]
    )
    logo = models.FileField(
        null=True, blank=True, upload_to='site_content/',
        validators=[
            FileExtensionValidator(allowed_extensions=['svg']),
            validate_file_infection
        ]
    )
    favicon = models.ImageField(
        null=True, blank=True, upload_to='site_content/',

        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES,
            ),
            validate_file_infection
        ]
    )

    translations = TranslatedFields(
        metadata_title=models.CharField(
            max_length=100, null=True, blank=True),
        metadata_description=models.TextField(
            null=True, blank=True),
        metadata_keywords=models.CharField(
            max_length=300, null=True, blank=True),
        start_page=models.CharField(
            max_length=100,
            null=True,
            blank=True,
            help_text=_('Slug of the start initiative page')
        ),

    )

    class Meta:
        verbose_name_plural = _('site platform settings')
        verbose_name = _('site platform settings')
示例#2
0
class PictureItem(ContentItem):
    """
    Picture content item
    """
    class PictureAlignment(DjangoChoices):
        float_left = ChoiceItem('float-left', label=_("Float left"))
        center = ChoiceItem('center', label=_("Center"))
        float_right = ChoiceItem('float-right', label=_("Float right"))

    image = ImageField(
        _("Picture"),
        upload_to='content_images',
        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES, ),
            validate_file_infection
        ])
    align = models.CharField(_("Align"),
                             max_length=50,
                             choices=PictureAlignment.choices)

    class Meta(object):
        verbose_name = _("Picture")
        verbose_name_plural = _("Pictures")

    def __str__(self):
        return self.image.name if self.image else u'(no image)'
示例#3
0
class File(AnonymizationMixin, models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created = models.DateField(_('created'), default=timezone.now)
    file = models.FileField(
        _('file'),
        upload_to='files',
        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.PRIVATE_FILE_ALLOWED_MIME_TYPES, ),
            validate_file_infection
        ])
    owner = models.ForeignKey(
        'members.Member',
        verbose_name=_('owner'),
        related_name='own_%(class)s',
    )
    used = models.BooleanField(_('used'), default=False)

    def __str__(self):
        return str(self.id)

    class JSONAPIMeta(object):
        resource_name = 'files'

    class Meta(object):
        abstract = True
示例#4
0
class Slide(SortableMixin, models.Model):
    block = models.ForeignKey('cms.SlidesContent', related_name='slides')
    tab_text = models.CharField(
        _("Tab text"), max_length=100,
        help_text=_("This is shown on tabs beneath the banner.")
    )
    title = models.CharField(_("Title"), max_length=100, blank=True)
    body = models.TextField(_("Body text"), blank=True)
    image = ImageField(
        _("Image"), max_length=255, blank=True, null=True,
        upload_to='banner_slides/',

        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES,
            ),
            validate_file_infection
        ]
    )
    background_image = ImageField(
        _("Background image"), max_length=255, blank=True,
        null=True, upload_to='banner_slides/',

        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES,
            ),
            validate_file_infection
        ]
    )
    video_url = models.URLField(
        _("Video url"), max_length=100, blank=True, default=''
    )
    link_text = models.CharField(
        _("Link text"), max_length=400,
        help_text=_("This is the text on the button inside the banner."),
        blank=True
    )
    link_url = models.CharField(
        _("Link url"), max_length=400,
        help_text=_("This is the link for the button inside the banner."),
        blank=True
    )
    sequence = models.PositiveIntegerField(default=0, editable=False, db_index=True)

    class Meta:
        ordering = ['sequence']
示例#5
0
class MemberPlatformSettings(BasePlatformSettings):
    LOGIN_METHODS = (
        ('password', _('Email/password combination')),
        ('SSO', _('Company SSO')),
    )

    closed = models.BooleanField(
        default=False,
        help_text=_('Require login before accessing the platform'))
    login_methods = MultiSelectField(max_length=100,
                                     choices=LOGIN_METHODS,
                                     default=['password'])
    confirm_signup = models.BooleanField(
        default=False,
        help_text=_('Require verifying the user\'s email before signup'))
    email_domain = models.CharField(
        blank=True,
        null=True,
        help_text=_('Domain that all email should belong to'),
        max_length=256)

    require_consent = models.BooleanField(
        default=False, help_text=_('Require users to consent to cookies'))
    consent_link = models.CharField(
        default='/pages/terms-and-conditions',
        help_text=_('Link more information about the platforms policy'),
        max_length=255)

    background = models.ImageField(
        null=True,
        blank=True,
        upload_to='site_content/',
        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES, ),
            validate_file_infection
        ])

    enable_segments = models.BooleanField(
        default=False,
        help_text=_('Enable segments for users e.g. department or job title.'))

    create_segments = models.BooleanField(
        default=False,
        help_text=_(
            'Create new segments when a user logs in. '
            'Leave unchecked if only priorly specified ones should be used.'))

    anonymization_age = models.IntegerField(
        default=0,
        help_text=
        _("The number of days after which user data should be anonymised. 0 for no anonymisation"
          ))

    class Meta(object):
        verbose_name_plural = _('member platform settings')
        verbose_name = _('member platform settings')
示例#6
0
class Category(TranslatableModel):
    slug = models.SlugField(_('slug'), max_length=100, unique=True)

    image = ImageField(_("image"),
                       max_length=255,
                       blank=True,
                       null=True,
                       upload_to='categories/',
                       help_text=_("Category image"))
    video = models.FileField(
        _("video"),
        max_length=255,
        blank=True,
        null=True,
        validators=[
            validate_video_file_size,
            FileMimetypeValidator(
                allowed_mimetypes=settings.VIDEO_FILE_ALLOWED_MIME_TYPES)
        ],
        help_text=_('This video will autoplay at the background. '
                    'Allowed types are mp4, ogg, 3gp, avi, mov and webm. '
                    'File should be smaller then 10MB.'),
        upload_to='banner_slides/')
    image_logo = ImageField(_("logo"),
                            max_length=255,
                            blank=True,
                            null=True,
                            upload_to='categories/logos/',
                            help_text=_("Category Logo image"))

    translations = TranslatedFields(title=models.CharField(_("name"),
                                                           max_length=255),
                                    description=models.TextField(
                                        _("description")))

    class Meta(object):
        verbose_name = _("category")
        verbose_name_plural = _("categories")
        # ordering = ['title']
        permissions = (('api_read_category',
                        'Can view categories through API'), )

    def __str__(self):
        return self.title

    def save(self, **kwargs):
        if not self.slug:
            self.slug = slugify(self.title)

        super(Category, self).save(**kwargs)

    def get_absolute_url(self):
        return 'https://{}/projects/?category={}'.format(
            properties.tenant.domain_url, self.slug)
示例#7
0
class NewsItem(AnonymizationMixin, PublishableModel):

    title = models.CharField(_("Title"), max_length=200)
    slug = models.SlugField(_("Slug"))

    # Contents
    main_image = ImageField(
        _("Main image"),
        help_text=_("Shows at the top of your post."),
        upload_to='blogs',
        blank=True,
        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES, ),
            validate_file_infection
        ])
    language = models.CharField(_("language"),
                                max_length=5,
                                choices=lazy(get_languages, tuple)())
    contents = PlaceholderField("blog_contents",
                                plugins=[
                                    'TextPlugin', 'ImageTextPlugin',
                                    'OEmbedPlugin', 'RawHtmlPlugin',
                                    'PicturePlugin'
                                ])
    # This should not be necessary, but fixes deletion of some news items
    # See https://github.com/edoburu/django-fluent-contents/issues/19
    contentitem_set = ContentItemRelation()

    allow_comments = models.BooleanField(_("Allow comments"), default=True)

    def __str__(self):
        return self.title

    def get_meta_description(self, **kwargs):
        request = kwargs.get('request')
        s = MLStripper()
        s.feed(mark_safe(render_placeholder(request, self.contents).html))
        return truncatechars(s.get_data(), 250)

    class Meta(object):
        verbose_name = _("news item")
        verbose_name_plural = _("news items")

        permissions = (
            ('api_read_newsitem', 'Can view news items through the API'),
            ('api_add_newsitem', 'Can add news items through the API'),
            ('api_change_newsitem', 'Can change news items through the API'),
            ('api_delete_newsitem', 'Can delete news items through the API'),
        )
示例#8
0
class Quote(models.Model):
    block = models.ForeignKey('cms.QuotesContent', related_name='quotes')
    name = models.CharField(max_length=60)
    quote = models.TextField()
    image = ImageField(
        _("Image"), max_length=255, blank=True, null=True,
        upload_to='quote_images/',

        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES,
            ),
            validate_file_infection
        ]
    )
示例#9
0
class MailPlatformSettings(BasePlatformSettings):
    email_logo = models.ImageField(
        null=True, blank=True, upload_to='site_content/',

        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES,
            ),
            validate_file_infection
        ]
    )

    class Meta(object):
        verbose_name_plural = _('mail platform settings')
        verbose_name = _('mail platform settings')
示例#10
0
class Logo(SortableMixin, models.Model):
    block = models.ForeignKey('cms.LogosContent', related_name='logos')
    image = ImageField(
        _("Image"), max_length=255, blank=True, null=True,
        upload_to='logo_images/',

        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES,
            ),
            validate_file_infection
        ]
    )
    link = models.CharField(max_length=100, blank=True, null=True)
    sequence = models.PositiveIntegerField(default=0, editable=False, db_index=True)

    class Meta:
        ordering = ['sequence']
示例#11
0
class Organization(ValidatedModelMixin, AnonymizationMixin, models.Model):
    """
    Organizations can run Projects. An organization has one or more members.
    """
    name = models.CharField(_('name'), max_length=255)
    slug = models.SlugField(_('slug'), max_length=100)
    description = models.TextField(_('description'), default='', blank=True)

    created = CreationDateTimeField(_('created'))
    updated = ModificationDateTimeField(_('updated'))

    owner = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('owner'), null=True)

    website = models.URLField(_('website'), blank=True)
    logo = ImageField(
        _('logo'),
        blank=True,
        help_text=_('Partner Organization Logo'),
        max_length=255,
        null=True,
        upload_to='partner_organization_logos/',

        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES,
            ),
            validate_file_infection
        ]
    )

    required_fields = ['name', 'website']

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.slug = slugify(self.name)

        super(Organization, self).save(*args, **kwargs)

    class Meta(object):
        ordering = ['name']
        verbose_name = _("partner organization")
        verbose_name_plural = _("partner organizations")
示例#12
0
class MediaWallpostPhoto(models.Model):
    mediawallpost = models.ForeignKey(MediaWallpost,
                                      related_name='photos',
                                      null=True,
                                      blank=True)
    results_page = models.BooleanField(default=True)
    photo = models.ImageField(
        upload_to='mediawallpostphotos',
        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES, ),
            validate_file_infection
        ])
    deleted = models.DateTimeField(_('deleted'), blank=True, null=True)
    ip_address = models.GenericIPAddressField(_('IP address'),
                                              blank=True,
                                              null=True,
                                              default=None)
    author = models.ForeignKey(settings.AUTH_USER_MODEL,
                               verbose_name=_('author'),
                               related_name="%(class)s_wallpost_photo",
                               blank=True,
                               null=True)
    editor = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name=_('editor'),
        blank=True,
        null=True,
        help_text=_("The last user to edit this wallpost photo."))

    @property
    def owner(self):
        return self.author

    @property
    def parent(self):
        return self.mediawallpost.content_object

    def __str__(self):
        return "Wallpost photo #{}".format(self.id)
示例#13
0
class Location(models.Model):
    name = models.CharField(_('name'), max_length=255)
    slug = models.SlugField(_('slug'), blank=False, null=True, max_length=255)

    position = GeopositionField(null=True)
    group = models.ForeignKey('geo.LocationGroup',
                              verbose_name=_('location group'),
                              null=True,
                              blank=True)
    city = models.CharField(_('city'), blank=True, null=True, max_length=255)
    country = models.ForeignKey('geo.Country', blank=True, null=True)
    description = models.TextField(_('description'), blank=True)
    image = ImageField(
        _('image'),
        max_length=255,
        null=True,
        blank=True,
        upload_to='location_images/',
        help_text=_('Location picture'),
        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES, ),
            validate_file_infection
        ])

    class Meta(GeoBaseModel.Meta):
        ordering = ['name']
        verbose_name = _('office location')
        verbose_name_plural = _('office locations')

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)

        super(Location, self).save()

    def __str__(self):
        return self.name
示例#14
0
class ResultPage(TranslatableModel):
    image = models.ImageField(
        _('Header image'), blank=True, null=True,

        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES,
            ),
            validate_file_infection
        ]
    )

    start_date = models.DateField(null=True, blank=True)
    end_date = models.DateField(null=True, blank=True)
    content = PlaceholderField('content', plugins=[
        'ProjectMapBlockPlugin',
        'QuotesBlockPlugin',
        'ActivitiesBlockPlugin',
        'ShareResultsBlockPlugin',
        'StatsBlockPlugin',
        'SupporterTotalBlockPlugin',
    ])

    translations = TranslatedFields(
        title=models.CharField(_('Title'), max_length=40),
        slug=models.SlugField(_('Slug'), max_length=40),
        description=models.CharField(_('Description'), max_length=45, blank=True, null=True)
    )

    class Meta:
        permissions = (
            ('api_read_resultpage', 'Can view result pages through the API'),
            ('api_add_resultpage', 'Can add result pages through the API'),
            ('api_change_resultpage', 'Can change result pages through the API'),
            ('api_delete_resultpage', 'Can delete result pages through the API'),
        )
示例#15
0
class Slide(PublishableModel):
    """
    Slides for homepage
    """
    class SlideStatus(DjangoChoices):
        published = ChoiceItem('published', label=_("Published"))
        draft = ChoiceItem('draft', label=_("Draft"))

    slug = models.SlugField(_("Slug"))
    language = models.CharField(_("language"),
                                max_length=5,
                                choices=lazy(get_languages, tuple)())
    tab_text = models.CharField(
        _("Tab text"),
        max_length=100,
        help_text=_("This is shown on tabs beneath the banner."))

    # Contents
    title = models.CharField(_("Title"), max_length=100, blank=True)
    body = models.TextField(_("Body text"), blank=True)
    image = ImageField(
        _("Image"),
        max_length=255,
        blank=True,
        null=True,
        upload_to='banner_slides/',
        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES, ),
            validate_file_infection
        ])
    background_image = ImageField(
        _("Background image"),
        max_length=255,
        blank=True,
        null=True,
        upload_to='banner_slides/',
        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES, ),
            validate_file_infection
        ])
    video = models.FileField(
        _("Video"),
        max_length=255,
        blank=True,
        null=True,
        validators=[
            validate_video_file_size,
            FileMimetypeValidator(
                allowed_mimetypes=settings.VIDEO_FILE_ALLOWED_MIME_TYPES)
        ],
        help_text=_('This video will autoplay at the background. '
                    'Allowed types are mp4, ogg, 3gp, avi, mov and webm. '
                    'File should be smaller then 10MB.'),
        upload_to='banner_slides/')
    video_url = models.URLField(_("Video url"),
                                max_length=100,
                                blank=True,
                                default='')
    link_text = models.CharField(
        _("Link text"),
        max_length=400,
        blank=True,
        help_text=_("This is the text on the button inside the banner."))
    link_url = models.CharField(
        _("Link url"),
        max_length=400,
        blank=True,
        help_text=_("This is the link for the button inside the banner."))
    style = models.CharField(_("Style"),
                             max_length=40,
                             help_text=_("Styling class name"),
                             default='default',
                             blank=True)

    # Metadata
    sequence = models.IntegerField()

    @property
    def background_image_full_path(self):
        return "{0}{1}".format(settings.MEDIA_URL, str(self.background_image))

    def __str__(self):
        return self.title

    class Meta(object):
        ordering = ('language', 'sequence')
示例#16
0
class BlueBottleBaseUser(AbstractBaseUser, PermissionsMixin):
    """
    Custom user model for BlueBottle.

    When extending the user model, the serializer should extend too.
    We provide a default base serializer in sync with the base user model
    The Django Meta attribute seems the best place for this configuration, so we
    have to add this.
    """
    class Gender(DjangoChoices):
        male = ChoiceItem('male', label=_('Male'))
        female = ChoiceItem('female', label=_('Female'))

    class UserType(DjangoChoices):
        person = ChoiceItem('person', label=_('Person'))
        company = ChoiceItem('company', label=_('Company'))
        foundation = ChoiceItem('foundation', label=_('Foundation'))
        school = ChoiceItem('school', label=_('School'))
        group = ChoiceItem('group', label=_('Club / association'))

    email = models.EmailField(_('email address'),
                              db_index=True,
                              max_length=254,
                              unique=True)
    username = models.CharField(_('username'), max_length=254, unique=True)

    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=False,
        help_text=_(
            'Designates whether this user should be treated as active. Unselect '
            'this instead of deleting accounts.'))
    disable_token = models.CharField(blank=True, max_length=32, null=True)

    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
    updated = ModificationDateTimeField()
    last_seen = models.DateTimeField(_('Last Seen'), blank=True, null=True)
    deleted = models.DateTimeField(_('deleted'), blank=True, null=True)

    user_type = models.CharField(_('Member Type'),
                                 choices=UserType.choices,
                                 default=UserType.person,
                                 max_length=25)

    first_name = models.CharField(_('first name'), blank=True, max_length=100)
    last_name = models.CharField(_('last name'), blank=True, max_length=100)
    place = models.CharField(_('Location your at now'),
                             blank=True,
                             max_length=100)
    location = models.ForeignKey('geo.Location',
                                 blank=True,
                                 help_text=_('Location'),
                                 null=True,
                                 on_delete=models.SET_NULL)
    favourite_themes = models.ManyToManyField(ProjectTheme, blank=True)
    skills = models.ManyToManyField('tasks.Skill', blank=True)
    phone_number = models.CharField(_('phone number'),
                                    blank=True,
                                    max_length=50)
    gender = models.CharField(_('gender'),
                              blank=True,
                              choices=Gender.choices,
                              max_length=6)
    birthdate = models.DateField(_('birthdate'), blank=True, null=True)
    about_me = models.TextField(_('about me'), blank=True, max_length=265)
    # TODO Use generate_picture_filename (or something) for upload_to
    picture = ImageField(
        _('picture'),
        blank=True,
        upload_to='profiles',
        validators=[
            FileMimetypeValidator(
                allowed_mimetypes=settings.IMAGE_ALLOWED_MIME_TYPES, ),
            validate_file_infection
        ])

    is_co_financer = models.BooleanField(
        _('Co-financer'),
        default=False,
        help_text=_(
            'Donations by co-financers are shown in a separate list on the '
            'project page. These donation will always be visible.'))
    can_pledge = models.BooleanField(
        _('Can pledge'),
        default=False,
        help_text=_('User can create a pledge donation.'))

    # Use lazy for the choices and default, so that tenant properties
    # will be correctly loaded
    primary_language = models.CharField(
        _('primary language'),
        choices=lazy(get_language_choices, tuple)(),
        default=lazy(get_default_language, str)(),
        help_text=_('Language used for website and emails.'),
        max_length=5)
    share_time_knowledge = models.BooleanField(_('share time and knowledge'),
                                               default=False)
    share_money = models.BooleanField(_('share money'), default=False)
    newsletter = models.BooleanField(_('newsletter'),
                                     default=True,
                                     help_text=_('Subscribe to newsletter.'))
    campaign_notifications = models.BooleanField(_('Project Notifications'),
                                                 default=True)

    website = models.URLField(_('website'), blank=True)
    facebook = models.CharField(_('facebook profile'),
                                blank=True,
                                max_length=50)
    twitter = models.CharField(_('twitter profile'), blank=True, max_length=15)
    skypename = models.CharField(_('skype profile'), blank=True, max_length=32)

    partner_organization = models.ForeignKey(
        'organizations.Organization',
        blank=True,
        null=True,
        help_text=_('Users that are connected to a partner organisation '
                    'will skip the organisation step in initiative create.'),
        related_name='partner_organization_members',
        verbose_name=_('Partner organisation'))

    is_anonymized = models.BooleanField(_('Is anonymized'), default=False)
    welcome_email_is_sent = models.BooleanField(_('Welcome email is sent'),
                                                default=False)

    USERNAME_FIELD = 'email'

    slug_field = 'username'

    objects = BlueBottleUserManager()

    class Meta(object):
        abstract = True
        verbose_name = _('member')
        verbose_name_plural = _('members')

        permissions = (
            ('api_read_member', 'Can view members through the API'),
            ('api_read_full_member', 'Can view full members through the API'),
            ('api_add_member', 'Can add members through the API'),
            ('api_change_member', 'Can change members through the API'),
            ('api_delete_member', 'Can delete members through the API'),
            ('api_read_own_member', 'Can view own members through the API'),
            ('api_change_own_member',
             'Can change own members through the API'),
            ('api_delete_own_member',
             'Can delete own members through the API'),
        )

    def update_deleted_timestamp(self):
        """ Automatically set or unset the deleted timestamp."""
        if not self.is_active and self.deleted is None:
            self.deleted = timezone.now()
        elif self.is_active and self.deleted is not None:
            self.deleted = None

    def generate_username(self):
        """ Generate and set a username if it hasn't already been set. """
        if not self.username:
            username = self.email
            original_username = username
            queryset = self.__class__.objects.all()
            if self.pk:
                queryset = queryset.exclude(pk=self.pk)

            # Increase the number while searching for the next valid slug
            # depending on the given slug, clean-up
            next_num = 2
            while queryset.filter(username=username):
                username = original_username
                end = str(next_num)
                username = '******'.format(username, end)
                next_num += 1

            # Finally set the generated username.
            self.username = username

    def clean(self):
        self.update_deleted_timestamp()
        self.generate_username()

    def get_full_name(self):
        """
        Returns the first_name plus the last_name, with a space in between.
        """
        full_name = u'{0} {1}'.format(self.first_name, self.last_name)
        return full_name.strip()

    def anonymize(self):
        self.is_active = False
        self.is_anonymized = True
        self.email = '{}[email protected]'.format(
            self.pk)  # disabled emails need to be unique too
        self.username = '******'.format(
            self.pk)  # disabled emails need to be unique too
        self.remote_id = '{}[email protected]'.format(
            self.pk)  # disabled emails need to be unique too
        self.set_unusable_password()
        self.first_name = 'Deactivated'
        self.last_name = 'Member'
        self.user_name = ''
        self.picture = ''
        self.avatar = ''
        self.about_me = ''
        self.gender = ''
        self.birthdate = '1000-01-01'
        self.location = None
        self.website = ''
        self.facebook = ''
        self.twitter = ''
        self.skypename = ''
        self.partner_organization = None

        self.save()

    @property
    def full_name(self):
        return self.get_full_name()

    def get_short_name(self):
        """
        The user is identified by their email address.
        """
        return self.first_name

    def email_user(self, subject, message, from_email=None):
        """
        Sends an email to this User with content type HTML.
        """
        # It's possible to send multi-part text / HTML email by following these
        # instructions: https://docs.djangoproject.com/en/1.5/topics/email
        # /#sending-alternative-content-types
        msg = EmailMessage(subject, message, from_email, [self.email])
        msg.content_subtype = 'html'  # Main content is now text/html
        msg.send()

    def get_jwt_token(self):
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER

        payload = jwt_payload_handler(self)
        token = jwt_encode_handler(payload)
        return token

    def get_login_token(self):
        return login_token_generator.make_token(self)

    @property
    def short_name(self):
        return self.get_short_name()

    @cached_property
    def is_initiator(self):
        return bool(self.own_initiatives.count())

    @cached_property
    def is_supporter(self):
        from bluebottle.funding.states import DonationStateMachine
        from bluebottle.funding.models import Donation
        return bool(
            self.contribution_set.instance_of(Donation).filter(
                status=DonationStateMachine.succeeded.value).count())

    @cached_property
    def is_volunteer(self):
        from bluebottle.assignments.models import Applicant
        from bluebottle.events.models import Participant
        from bluebottle.activities.states import ActivityStateMachine
        return bool(
            self.contribution_set.instance_of(Applicant, Participant).filter(
                status=ActivityStateMachine.succeeded.value).count())

    @cached_property
    def amount_donated(self):
        from bluebottle.funding.states import DonationStateMachine
        from bluebottle.funding.models import Donation
        from bluebottle.funding.utils import calculate_total
        donations = self.contribution_set.instance_of(Donation).filter(
            status=DonationStateMachine.succeeded.value)
        return calculate_total(donations)

    @cached_property
    def time_spent(self):
        from bluebottle.assignments.models import Applicant
        from bluebottle.events.models import Participant
        from bluebottle.activities.states import ActivityStateMachine
        contributions = self.contribution_set.instance_of(Applicant, Participant).\
            filter(status=ActivityStateMachine.succeeded.value).all()
        return sum([c.time_spent for c in contributions])

    @cached_property
    def subscribed(self):
        return self.campaign_notifications

    def reset_disable_token(self):
        # Generates a random UUID and converts it to a 32-character
        # hexidecimal string
        token = uuid.uuid4().hex
        self.disable_token = token
        self.save()

    def get_disable_token(self):
        if not self.disable_token:
            self.reset_disable_token()
        return self.disable_token

    def save(self,
             force_insert=False,
             force_update=False,
             using=None,
             update_fields=None):
        self.generate_username()

        super(BlueBottleBaseUser, self).save(force_insert, force_update, using,
                                             update_fields)

    def __getattr__(self, name):
        # Magically get extra fields
        if name.startswith('extra_'):
            name = name.replace('extra_', '')
            return self.extra.get(field__name=name).value
        return super(BlueBottleBaseUser, self).__getattribute__(name)