Example #1
0
class User(PermissionsMixin, AbstractBaseUser):
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name']

    email = models.EmailField(unique=True, verbose_name=_('email'))
    first_name = models.CharField(max_length=100, verbose_name=_('firstname'))
    last_name = models.CharField(max_length=100, verbose_name=_('lastname'))
    is_staff = models.BooleanField(default=False, verbose_name=_('is staff'))
    date_joined = models.DateTimeField(auto_now_add=True,
                                       verbose_name=_('date joined'))
    picture = thumbnail.ImageField(
        upload_to=upload_to,
        null=True,
        blank=True,
        verbose_name=_('picture'),
        help_text=_('Please upload a picture of at least 200x200px'),
    )

    objects = UsernameLessUserManager()

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

    def get_full_name(self):
        return '{} {}'.format(self.first_name, self.last_name)

    def get_short_name(self):
        return self.first_name
Example #2
0
class Profile(models.Model):
    """
    A Profile model that extends the User model in django auth
    """
    GENDER_CHOICE = (
        ('Male', 'Male'),
        ('Female', 'Female'),
        ('Unknown', "Don't specify"),
    )

    user = models.OneToOneField(User, on_delete=models.CASCADE)
    image = thumbnail.ImageField('Profile Pic',
                                 upload_to='accounts_img',
                                 default='accounts_img/logo.PNG')
    gender = models.CharField(max_length=30,
                              choices=GENDER_CHOICE,
                              default='Unknown')
    address1 = models.CharField("Address line 1", max_length=1024, null=True)
    address2 = models.CharField("Address line 2", max_length=1024, null=True)
    city = models.CharField(max_length=1024, null=True)
    postcode = models.CharField(max_length=12, null=True)
    session_token = models.IntegerField(default=0)

    def __str__(self):
        return f'{self.user.username} Profile'

    @property
    def thumbnail(self):
        if self.image:
            return thumbnail.get_thumbnail(self.image, '50x50', quality=90)
        return None
Example #3
0
class FixtureData(models.Model):
    lot_number = models.CharField(max_length=20)
    image_data = thumbnail.ImageField(upload_to='fixture/', blank=True)
    message = models.CharField(max_length=20, default='unknown')
    uploaded_date = models.DateTimeField(unique=True)
    last_modified = models.DateTimeField(auto_now=True)
    rle_csv = models.ImageField(upload_to='fixture_rle/', blank=True)

    def get_absolute_url(self):
        return '../add/'
Example #4
0
File: models.py Project: g10f/sso
class OrganisationPicture(AbstractBaseModel):
    MAX_PICTURE_SIZE = 5242880  # 5 MB
    organisation = models.ForeignKey(Organisation, on_delete=models.CASCADE)
    title = models.CharField(_("title"), max_length=255, blank=True)
    description = models.TextField(_("description"), blank=True, max_length=2048)
    picture = thumbnail.ImageField(_('picture'), upload_to=generate_filename)  # , storage=MediaStorage())
    order = models.IntegerField(default=0, help_text=_('Overwrites the alphabetic order.'))

    class Meta(AbstractBaseModel.Meta):
        verbose_name = _('organisation picture')
        verbose_name_plural = _('organisation pictures')
        ordering = ['order']
Example #5
0
class Screenshot(models.Model):
    """A screenshot displayed in the media gallery, which can optionally be
    promoted to the homepage"""

    game_name = models.CharField(max_length=64)
    description = models.TextField(blank=True)
    image = thumbnail.ImageField(upload_to='screenshots')
    displayed = models.BooleanField(default=True)
    promoted = models.BooleanField(default=False)

    def __unicode__(self):
        return self.game_name

    def get_absolute_url(self):
        return self.image.url
Example #6
0
class Category(models.Model):
    image = thumbnail.ImageField(upload_to='categories', default='default_image')
    name = models.CharField(max_length = 200)
    slug = models.SlugField(unique = True)
    order = models.IntegerField('Position', default=0)

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

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name_plural = "categories"
Example #7
0
class User(AbstractUser):
    name = models.CharField(_("Name"), blank=True, max_length=255)
    avatar = thumbnail.ImageField(blank=True, null=True)

    def __str__(self):
        return self.username
    
    # returns name if set, or username if else
    def user_avail_name(self):
        if self.name:
            return self.name
        return self.username


    def get_absolute_url(self):
        return reverse("users:detail", kwargs={"username": self.username})
Example #8
0
class Variable(models.Model):
    """
    A variable that we are reporting on
    """
    SCORE = 'sc'
    BINARY = 'bi'
    COUNT = 'co'
    TIME = 'ti'

    STYLE_CHOICES = ((SCORE, 'Score'), (BINARY, 'Binary'), (COUNT, 'Count'),
                     (TIME, 'Time'))

    trial = models.ForeignKey(Trial)
    name = models.CharField(max_length=200, blank=True, null=True)
    question = models.TextField(blank=True, null=True)
    style = models.CharField(max_length=2,
                             choices=STYLE_CHOICES,
                             default=SCORE)
    image = thumbnail.ImageField(upload_to='variableuploads',
                                 blank=True,
                                 null=True)

    def __unicode__(self):
        return u'<Variable {0} ({1})>'.format(self.name, self.style)

    def report_form(self):
        """
        Return the relevant report form with this as it's
        instance.

        Return: Form
        Exceptions: None
        """
        from rm.trials import forms
        data = dict(trial=self.trial, variable=self)
        return forms.reportform_factory(self, data)

    def duplicate(self):
        """
        Return a new variable un-linked to this trial

        Return: Variable
        Exceptions: None
        """
        return Variable(name=self.name,
                        question=self.question,
                        style=self.style)
Example #9
0
class Locality(SlugMixin, MPTTModel):
    title = models.CharField(_('title'), max_length=100, unique=True)
    slug = models.SlugField(unique=True, max_length=SlugMixin.MAX_SLUG_LENGTH, default='')
    description = models.TextField(_('description'), blank=True)
    parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
    photo = thumbnail.ImageField(
        verbose_name=_('photo'),
        max_length=1024 * 5,
        upload_to='localities',
        blank=True, null=True, default=None
    )
    i18n = TranslationField(fields=('description',))

    class MPTTMeta:
        order_insertion_by = ['title']

    class Meta:
        verbose_name = _('locality')
        verbose_name_plural = _('localities')
        ordering = ('title',)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        from inventor.core.listings.models.general import Listing
        url = Listing.get_list_url()
        return f'{url}?locality={self.slug}'

    def delete(self, **kwargs):
        """ Deletes file before deleting instance """
        self.delete_file()
        super().delete(**kwargs)

    def delete_file(self):
        """ Deletes image file """
        try:
            os.remove(self.photo.path)
        except ValueError:
            pass
        except IOError:
            pass
        except OSError:
            pass
Example #10
0
class Profile(models.Model):
    """
    A Profile model that extends the User model in django auth
    """
    GENDER_CHOICE = (
        ('M', 'Male'),
        ('F', 'Female'),
        ('U', "Don't specify"),
    )

    user = models.OneToOneField(User, on_delete=models.CASCADE)
    gender = models.CharField(max_length=2, choices=GENDER_CHOICE, default='U')
    address1 = models.CharField("Address line 1",
                                max_length=1024,
                                null=True,
                                blank=True)
    address2 = models.CharField("Address line 2",
                                max_length=1024,
                                null=True,
                                blank=True)
    city = models.CharField(max_length=1024, null=True, blank=True)
    postcode = models.CharField(max_length=12, null=True, blank=True)
    mobile = PhoneField(blank=True, help_text='Contact phone number')
    dob = models.DateField(null=True, blank=True)
    height = models.DecimalField(max_digits=5,
                                 decimal_places=2,
                                 help_text='Height in CM',
                                 blank=True,
                                 null=True)
    image = thumbnail.ImageField(
        'Profile Pic',
        upload_to='images/accounts',
        default='images/accounts/joe-paris-logo-coming-soon.png')

    def __str__(self):
        return f'{self.user.username} Profile'

    @property
    def thumbnail(self):
        if self.image:
            return thumbnail.get_thumbnail(self.image, '50x50', quality=90)
        return None
Example #11
0
class Image(models.Model):
    image_file = thumbnail.ImageField(upload_to='products')
    product = models.ForeignKey(Product)
    description = models.TextField(null=True, blank=True)

    def image_tag(self):
        return u'<img width="110" height="110" src="%s" />' % self.image_file.url
    image_tag.short_description = 'Image'
    image_tag.allow_tags = True

    # for grapelli ordering
    position = models.PositiveSmallIntegerField("Position", default=0)
    class Meta:
        ordering = ['position']

    def __unicode__(self):
        if self.description:
            return self.description
        else:
            return unicode(self.image_file)
Example #12
0
class Group(SlugMixin, models.Model):
    # definition
    title = models.CharField(_('title'), max_length=100)
    slug = models.SlugField(unique=True, max_length=SlugMixin.MAX_SLUG_LENGTH, blank=True)
    description = models.TextField(_('description'), blank=True)

    # previews
    image = thumbnail.ImageField(
        verbose_name=_('image'),
        help_text=_('photo or image'),
        max_length=1024,
        upload_to='images',
        blank=True
    )

    banner = models.ImageField(
        verbose_name=_('banner'),
        help_text=_('photo or image'),
        max_length=1024 * 5,
        upload_to='banners',
        blank=True
    )

    video_url = models.URLField(_('video URL'), max_length=300, blank=True)
    listings = models.ManyToManyField(to=Listing, verbose_name=_('listings'), blank=True, related_name='groups')
    i18n = TranslationField(fields=('slug', 'title', 'description',))

    class Meta:
        verbose_name = _('group')
        verbose_name_plural = _('groups')
        ordering = ('title',)
        indexes = [GinIndex(fields=["i18n"]), ]

    def __str__(self):
        return self.title_i18n

    def get_absolute_url(self):
        return reverse('listings:group_detail', args=(self.slug_i18n,))
Example #13
0
class Seo(models.Model):
    ROBOTS = [
        ('index', 'index'),
        ('noindex', 'noindex'),
        ('follow', 'follow'),
        ('noindex, follow', 'noindex, follow'),
        ('index, nofollow', 'index, nofollow'),
        ('noindex, nofollow', 'noindex, nofollow'),
    ]

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True, default=None)
    object_id = models.PositiveIntegerField(blank=True, null=True, default=None)
    content_object = GenericForeignKey('content_type', 'object_id')
    path = models.CharField(verbose_name=_('request path'), max_length=100, blank=True, default='')

    title = models.CharField(verbose_name=_('title'), max_length=200)
    description = models.CharField(verbose_name=_('description'), max_length=200)
    keywords = models.CharField(verbose_name=_('keywords'), max_length=1000)
    robots = models.CharField(verbose_name=_('robots'), choices=ROBOTS, max_length=17, blank=True, default='',
        help_text=_('Default (empty) = index, follow'))
    image = thumbnail.ImageField(
        verbose_name=_('image'),
        help_text=_('Min resolution: 200 x 200 px. Suggested resolution: 1200 x 630 px (1.9:1). <a href="https://developers.facebook.com/docs/sharing/webmasters/images/" target="_blank">Read more</a>.'),
        max_length=8*1024,
        upload_to='images',
        blank=True
    )
    objects = SeoQuerySet.as_manager()
    i18n = TranslationField(fields=('title', 'description', 'keywords', 'path'))

    class Meta:
        verbose_name = _('SEO fields')
        verbose_name_plural = _('SEO fields')
        unique_together = ('content_type', 'object_id', 'path')
        indexes = [GinIndex(fields=["i18n"]), ]

    def __str__(self):
        return self.title_i18n
Example #14
0
class Article(SlugMixin, models.Model):
    FORCE_SLUG_REGENERATION = False  # To preserve SEO links

    category = models.ForeignKey(Category, verbose_name=_('category'), on_delete=models.PROTECT, blank=True, null=True, default=None)
    title = models.CharField(_('title'), max_length=100, unique=True, db_index=True)
    slug = models.SlugField(unique=True, max_length=150, blank=True, db_index=True)
    author = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('author'), on_delete=models.PROTECT)
    is_highlighted = models.BooleanField(_('highlighted'), default=False)
    is_published = models.BooleanField(_('published'), default=True)
    seen = models.PositiveSmallIntegerField(_('seen'), db_index=True, default=0)
    image = thumbnail.ImageField(
        verbose_name=_('image'),
        help_text=_('photo or image'),
        max_length=1024,
        upload_to='images',
        blank=True
    )
    excerpt = models.TextField(_('excerpt'), help_text=_('preview'))
    content = MartorField(_('content'))
    language = models.CharField(_('language'), max_length=2, db_index=True, blank=True)
    publish_date = models.DateField(_(u'publish date'), blank=True, null=True, default=None)
    created = models.DateTimeField(_(u'created'), auto_now_add=True)
    modified = models.DateTimeField(_(u'modified'), auto_now=True)
    objects = ArticleQuerySet.as_manager()

    class Meta:
        verbose_name = _(u'article')
        verbose_name_plural = _(u'articles')
        ordering = ['title']
        default_permissions = settings.DEFAULT_PERMISSIONS

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('writing:article_detail', kwargs={
            'slug': slugify(self.slug)
        })
Example #15
0
class CustomUser(models.Model):

    user = models.OneToOneField(USER,
                                on_delete=models.CASCADE,
                                related_name='profile')

    profile_picture = thumbnail.ImageField(upload_to='documents/', blank=True)

    email = models.EmailField(
        _('email address'),
        null=True,
        unique=True,
    )

    gender = models.CharField(max_length=10,
                              choices=GENDER_CHOICES,
                              blank=True,
                              null=True)

    martial_status = models.CharField(max_length=10,
                                      choices=MARTIAL_CHOICES,
                                      blank=True,
                                      null=True)
    dob = models.DateField(_("Date of Birth"), blank=True, null=True)

    current_address = models.ForeignKey(Location,
                                        null=True,
                                        blank=True,
                                        related_name='current_address_users',
                                        on_delete=models.SET_NULL)

    permanent_address = models.ForeignKey(
        Location,
        null=True,
        blank=True,
        related_name='permanent_address_users',
        on_delete=models.SET_NULL)
Example #16
0
class Race(Listing):
    section = _('sport')
    distance = models.DecimalField(_('distance'),
                                   help_text='km',
                                   max_digits=6,
                                   decimal_places=2,
                                   validators=[MinValueValidator(0)],
                                   blank=True,
                                   null=True,
                                   default=None)
    elevation = models.PositiveSmallIntegerField(_('elevation'),
                                                 help_text='m',
                                                 blank=True,
                                                 null=True,
                                                 default=None)
    # date = models.DateField(_('date'), blank=True, null=True, default=None)  # TODO: range field?
    medal = thumbnail.ImageField(verbose_name=_('medal'),
                                 help_text=_('photo or image'),
                                 max_length=1024,
                                 upload_to='images',
                                 blank=True)

    class Meta:
        verbose_name = _('race')
        verbose_name_plural = _('races')
        ordering = ('title', )

    class URL:
        slug = pgettext_lazy('url', 'races/')

    def get_distance_display(self):
        # TODO: settings for DECIMAL PLACES
        return f'{floatformat(self.distance, -1)} km' if self.distance else ''

    def get_elevation_display(self):
        return f'{self.elevation} m' if self.elevation else ''
Example #17
0
class ImageFile(TimeStampedModel, Edit_url_mixin, AutoCropImage):
    class Meta:
        verbose_name = _('ImageFile')
        verbose_name_plural = _('ImageFiles')

    objects = ImageFileManager()

    source_file = thumbnail.ImageField(
        verbose_name=_('source file'),
        upload_to=upload_image_to,
        height_field='full_height',
        width_field='full_width',
        max_length=1024,
    )
    _md5 = models.CharField(
        verbose_name=_('md5'),
        help_text=_('md5 hash of source file'),
        max_length=32,
        editable=False,
        null=True,
    )
    _size = models.PositiveIntegerField(
        verbose_name=_('file size'),
        help_text=_('size of file in bytes'),
        editable=False,
        null=True,
    )
    _mtime = models.PositiveIntegerField(
        verbose_name=_('timestamp'),
        help_text=_('mtime timestamp of source file'),
        editable=False,
        null=True,
    )
    full_height = models.PositiveIntegerField(
        verbose_name=_('height'),
        help_text=_('full height in pixels'),
        null=True,
        editable=False,
    )
    full_width = models.PositiveIntegerField(
        verbose_name=_('width'),
        help_text=_('full height in pixels'),
        null=True,
        editable=False,
    )
    from_top = models.PositiveSmallIntegerField(
        verbose_name=_('from top'),
        default=50,
        help_text=_('image crop vertical. Between 0% and 100%.'),
        validators=[MaxValueValidator(100),
                    MinValueValidator(0)],
    )
    from_left = models.PositiveSmallIntegerField(
        verbose_name=_('from left'),
        default=50,
        help_text=_('image crop horizontal. Between 0% and 100%.'),
        validators=[MaxValueValidator(100),
                    MinValueValidator(0)],
    )
    crop_diameter = models.PositiveSmallIntegerField(
        verbose_name=_('crop diameter'),
        default=100,
        help_text=_(
            'area containing most relevant content. Area is considered a '
            'circle with center x,y and diameter d where x and y are the '
            'values "from_left" and "from_right" and d is a percentage of '
            'the shortest axis. This is used for close cropping of some '
            'images, for instance byline photos.'),
        validators=[MaxValueValidator(100),
                    MinValueValidator(0)],
    )

    old_file_path = models.CharField(
        verbose_name=_('old file path'),
        help_text=_('previous path if the image has been moved.'),
        blank=True,
        null=True,
        max_length=1000)

    contributor = models.ForeignKey(
        'contributors.Contributor',
        verbose_name=_('contributor'),
        help_text=_('who made this'),
        blank=True,
        null=True,
    )

    copyright_information = models.CharField(
        verbose_name=_('copyright information'),
        help_text=_(
            'extra information about license and attribution if needed.'),
        blank=True,
        null=True,
        max_length=1000,
    )

    def __str__(self):
        if self.source_file:
            return os.path.basename(self.source_file.name)
        else:
            return super(ImageFile, self).__str__()

    def save(self, *args, **kwargs):
        self.md5, self.size, self.mtime = (None, None, None)
        # refresh values
        self.md5, self.size, self.mtime
        try:
            saved = type(self).objects.get(id=self.pk)
        except ImageFile.DoesNotExist:
            pass
        else:
            if all((self.cropping_method != self.CROP_PENDING,
                    saved.cropping_method != self.CROP_PENDING,
                    saved.cropping != self.cropping)):
                self.cropping_method = self.CROP_MANUAL
        super().save(*args, **kwargs)

    def save_local_image_as_source(self, filepath, save=True):
        """Save file from local filesystem as source

        Only saves if the new file is different from the one that exists.
        """
        mtime = os.stat(filepath).st_mtime
        size = os.stat(filepath).st_size
        md5 = local_md5(filepath)
        if self.pk and self.source_file:
            if mtime <= self.mtime or (size, md5) == (self.size, self.md5):
                return False
        filename = os.path.split(filepath)[1]
        with open(filepath, 'rb') as source:
            content = File(source)
            self.source_file.save(filename, content, save)
        return True

    @property
    def md5(self):
        """Calculate or retrieve md5 value"""
        if self.source_file is None:
            return None
        if self._md5 is None:
            self._md5 = file_field_md5(self.source_file)
            # try:  # Locally stored file
            #     self._md5 = local_md5(self.source_file.path)
            # except NotImplementedError:  # AWS S3 storage
            #     self._md5 = s3_md5(self.source_file.file.key)
        return self._md5

    @md5.setter
    def md5(self, value):
        self._md5 = value

    @property
    def size(self):
        """Calculate or retrive filesize"""
        if self.source_file is None:
            return None
        if self._size is None:
            self._size = self.source_file.size
        return self._size

    @size.setter
    def size(self, value):
        self._size = value

    @property
    def mtime(self):
        """Modified time as unix timestamp"""
        if self.source_file is None:
            return None
        if self._mtime is None:
            try:
                try:  # Locally stored file
                    mtime = os.path.getmtime(self.source_file.path)
                    self._mtime = int(mtime)
                except NotImplementedError:  # AWS S3 storage
                    key = self.source_file.file.key
                    modified = boto.utils.parse_ts(key.last_modified)
                    self._mtime = int(modified.strftime('%s'))
            except (FileNotFoundError, AttributeError):
                self._mtime = int(timezone.now().strftime('%s'))
        return self._mtime

    @mtime.setter
    def mtime(self, timestamp):
        self._mtime = timestamp

    @classmethod
    def upload_folder(cls):
        issue = current_issue().issue_tuple()
        return os.path.join(str(issue.date.year), str(issue.number))

    @staticmethod
    def slugify(filename):
        slugify = Slugify(safe_chars='.-', separator='-')
        slugs = slugify(filename).split('.')
        slugs[-1] = slugs[-1].lower().replace('jpeg', 'jpg')
        slug = '.'.join(segment.strip('-') for segment in slugs)
        return slug

    def thumb(self, height=315, width=600):
        geometry = '{}x{}'.format(width, height)
        try:
            return thumbnail.get_thumbnail(self.source_file,
                                           geometry,
                                           crop=self.get_crop()).url
        except Exception as e:
            msg = 'Thumbnail failed: {} {}'.format(e, self.source_file)
            logger.warn(msg)
            return self.source_file

    @property
    def cropping(self):
        return Cropping(top=self.from_top,
                        left=self.from_left,
                        diameter=self.crop_diameter)

    @cropping.setter
    def cropping(self, _crop):
        crop = Cropping(*_crop)
        self.from_top = crop.top
        self.from_left = crop.left
        self.crop_diameter = crop.diameter

    @cropping.deleter
    def cropping(self):
        field_names = ('from_top', 'from_left', 'crop_diameter',
                       'cropping_method')
        for field_name in field_names:
            field = self._meta.get_field(field_name)
            setattr(self, field.name, field.default)

    def get_crop(self):
        """ return center point of image in percent from top and left. """
        if self.cropping_method == self.CROP_NONE:
            self.autocrop()
        return '{h}% {v}%'.format(h=self.from_left, v=self.from_top)
Example #18
0
class User(AbstractBaseUser, PermissionsMixin):
    MAX_PICTURE_SIZE = settings.SSO_USER_MAX_PICTURE_SIZE
    PICTURE_WIDTH = settings.SSO_USER_PICTURE_WIDTH
    PICTURE_HEIGHT = settings.SSO_USER_PICTURE_HEIGHT
    GENDER_CHOICES = [('m', _('male')), ('f', _('female'))]
    username_validator = UnicodeUsernameValidator()

    username = models.CharField(
        _('username'),
        max_length=150,
        unique=True,
        help_text=
        _('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'
          ),
        validators=[username_validator],
        error_messages={
            'unique': _("A user with that username already exists.")
        })
    first_name = models.CharField(_('first name'), max_length=150, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=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=True,
        db_index=True,
        help_text=
        _('Designates whether this user should be treated as active. Unselect this instead of deleting accounts.'
          ))
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
    uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=True)
    organisations = models.ManyToManyField(
        Organisation,
        verbose_name=_('organisations'),
        through=Membership,
        blank=(not settings.SSO_ORGANISATION_REQUIRED))
    admin_regions = models.ManyToManyField(AdminRegion,
                                           verbose_name=_('admin regions'),
                                           blank=True)
    admin_organisation_countries = models.ManyToManyField(
        OrganisationCountry, verbose_name=_('admin countries'), blank=True)
    admin_associations = models.ManyToManyField(
        Association, verbose_name=_('admin associations'), blank=True)
    app_admin_regions = models.ManyToManyField(
        AdminRegion,
        related_name='app_admin_user',
        verbose_name=_('app admin regions'),
        blank=True)
    app_admin_organisation_countries = models.ManyToManyField(
        OrganisationCountry,
        related_name='app_admin_user',
        verbose_name=_('app admin countries'),
        blank=True)
    app_admin_associations = models.ManyToManyField(
        Association,
        related_name='app_admin_user',
        verbose_name=_('app admin associations'),
        blank=True)
    application_roles = models.ManyToManyField(
        ApplicationRole, verbose_name=_('application roles'), blank=True)
    role_profiles = models.ManyToManyField(
        RoleProfile,
        verbose_name=_('role profiles'),
        blank=True,
        help_text=_('Organises a group of application roles that are usually '
                    'assigned together.'))
    last_modified_by_user = CurrentUserField(
        verbose_name=_('last modified by'),
        related_name='+',
        on_delete=models.SET_NULL)
    last_modified = models.DateTimeField(_('last modified'), auto_now=True)
    created_by_user = models.ForeignKey('self',
                                        verbose_name=_('created by'),
                                        related_name='+',
                                        null=True,
                                        on_delete=models.SET_NULL)
    is_center = models.BooleanField(
        _('organisation'),
        default=False,
        help_text=_(
            'Designates that this user is representing a organisation and not a '
            'private person.'))
    is_service = models.BooleanField(
        _('service'),
        default=False,
        help_text=_(
            'Designates that this user is representing a service account and '
            'not a person.'))
    is_subscriber = models.BooleanField(
        _('subscriber'),
        default=False,
        help_text=_(
            'Designates whether this user is a newsletter subscriber.'))
    picture = thumbnail.ImageField(_('picture'),
                                   upload_to=generate_filename,
                                   blank=True)  # , storage=MediaStorage())
    gender = models.CharField(_('gender'),
                              max_length=255,
                              choices=GENDER_CHOICES,
                              blank=True)
    dob = models.DateField(_("date of birth"), blank=True, null=True)
    homepage = models.URLField(_("homepage"), max_length=512, blank=True)
    language = models.CharField(_('language'),
                                max_length=254,
                                choices=settings.LANGUAGES,
                                blank=True)
    timezone = models.CharField(_('timezone'), blank=True, max_length=254)
    valid_until = models.DateTimeField(_('valid until'), blank=True, null=True)
    last_ip = models.GenericIPAddressField(_('last ip address'),
                                           blank=True,
                                           null=True)
    is_stored_permanently = models.BooleanField(
        _('store permanently'),
        help_text=_('Do not delete, even if inactive'),
        default=False)

    objects = UserManager()

    USERNAME_FIELD = 'username'

    class Meta(AbstractBaseUser.Meta):
        verbose_name = _('user')
        verbose_name_plural = _('users')
        permissions = (
            ("read_user", "Can read user data"),
            ("access_all_users", "Can access all users"),
            ("app_admin_access_all_users",
             "Can access all users as App admin"),
        )

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

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

    def email_user(self, subject, message, from_email=None, **kwargs):
        """
        Sends an email to this User.
        """
        if self.primary_email() is not None:
            recipient_list = [self.primary_email().email]
            if from_email is not None:
                from_email = force_str(from_email)
            return send_mail(subject,
                             message,
                             recipient_list,
                             from_email=from_email,
                             **kwargs)
        else:
            logger.error('User %s has no primary_email', self.username)
        return 0

    def primary_email(self):
        # iterate through useremail_set.all because useremail_set is cached
        # if we use prefetch_related('useremail_set')
        for user_mail in self.useremail_set.all():
            if user_mail.primary:
                return user_mail
        return None

    def create_primary_email(self, email, confirmed=None, delete_others=False):
        """
        make email as the primary email and all other emails non primary
        if the user email does not exist, it is created
        the other user emails are marked as not primary or deleted
        """
        email = UserManager.normalize_email(email)
        user_email = None
        for l_user_email in self.useremail_set.all():
            if email.lower() == l_user_email.email.lower():
                l_user_email.primary = True
                l_user_email.email = email
                if confirmed is not None:
                    l_user_email.confirmed = confirmed
                l_user_email.save()

                user_email = l_user_email
            else:
                if delete_others:
                    l_user_email.delete()
                else:
                    if l_user_email.primary:
                        l_user_email.primary = False
                        l_user_email.save(update_fields=['primary'])
        if not user_email:
            kwargs = {'email': email, 'user': self, 'primary': True}
            if confirmed is not None:
                kwargs['confirmed'] = confirmed
            user_email = UserEmail.objects.create(**kwargs)
        return user_email

    def confirm_primary_email_if_no_confirmed(self):
        if not UserEmail.objects.filter(confirmed=True, user=self).exists():
            # no confirmed email addresses for this user, then the password reset
            # must be send to the primary email and we can mark this email as confirmed
            user_email = UserEmail.objects.get(primary=True, user=self)
            assert (not user_email.confirmed)
            user_email.confirmed = True
            user_email.save(update_fields=['confirmed'])

    def ensure_single_primary_email(self):
        ensure_single_primary(self.useremail_set.all())

    def update_last_modified(self):
        self.save(update_fields=['last_modified'])

    @memoize
    def get_last_modified_deep(self):
        """
        get the max date of last_modified from user and corresponding address and phones
        and use _prefetched_objects_cache if available for performance in api lists
        """
        last_modified_list = [self.last_modified]
        if hasattr(self, '_prefetched_objects_cache') and (
                'useraddress_set' in self._prefetched_objects_cache):
            last_modified_list += [
                obj.last_modified for obj in self.useraddress_set.all()
            ]
        else:
            last_modified_list += self.useraddress_set.values_list(
                "last_modified", flat=True)

        if hasattr(self, '_prefetched_objects_cache') and (
                'userphonenumber_set' in self._prefetched_objects_cache):
            last_modified_list += [
                obj.last_modified for obj in self.userphonenumber_set.all()
            ]
        else:
            last_modified_list += self.userphonenumber_set.values_list(
                "last_modified", flat=True)

        if hasattr(self, '_prefetched_objects_cache') and (
                'useremail_set' in self._prefetched_objects_cache):
            last_modified_list += [
                obj.last_modified for obj in self.useremail_set.all()
            ]
        else:
            last_modified_list += self.useremail_set.values_list(
                "last_modified", flat=True)

        if hasattr(self, '_prefetched_objects_cache') and (
                'userattribute_set' in self._prefetched_objects_cache):
            last_modified_list += [
                obj.last_modified for obj in self.userattribute_set.all()
            ]
        else:
            last_modified_list += self.userattribute_set.values_list(
                "last_modified", flat=True)

        last_modified = max(last_modified_list)
        return last_modified

    @classmethod
    def get_primary_or_none(cls, queryset):
        # iterate through all items, uses the prefetch_related cache
        for item in queryset:
            if item.primary:
                return item
        return None

    @classmethod
    def get_default_role_profile(cls, role_uuid=None):
        role_profile = RoleProfile.objects.none()
        if role_uuid is None:
            role_uuid = settings.SSO_DEFAULT_MEMBER_PROFILE_UUID
        if role_uuid:
            try:
                role_profile = RoleProfile.objects.get(uuid=role_uuid)
            except ObjectDoesNotExist:
                pass
        return role_profile

    @classmethod
    def get_default_guest_profile(cls, role_uuid=None):
        role_profile = None
        if role_uuid is None:
            role_uuid = settings.SSO_DEFAULT_GUEST_PROFILE_UUID
        if role_uuid:
            try:
                role_profile = RoleProfile.objects.get(uuid=role_uuid)
            except ObjectDoesNotExist:
                pass
        return role_profile

    @classmethod
    def get_default_admin_profile(cls):
        role_profile = RoleProfile.objects.none()
        if settings.SSO_DEFAULT_ADMIN_PROFILE_UUID:
            try:
                role_profile = RoleProfile.objects.get(
                    uuid=settings.SSO_DEFAULT_ADMIN_PROFILE_UUID)
            except ObjectDoesNotExist:
                pass
        return role_profile

    @property
    def primary_address(self):
        return self.get_primary_or_none(self.useraddress_set.all())

    @property
    def primary_phone(self):
        return self.get_primary_or_none(self.userphonenumber_set.all())

    @memoize
    def get_apps(self):
        applicationrole_ids = self.get_applicationrole_ids()
        return Application.objects.distinct().filter(applicationrole__in=applicationrole_ids, is_active=True). \
            order_by('order').prefetch_related('applicationrole_set', 'applicationrole_set__role')

    def get_global_navigation_urls(self):
        applicationrole_ids = self.get_applicationrole_ids()
        return Application.objects.distinct().filter(
            applicationrole__in=applicationrole_ids,
            is_active=True,
            global_navigation=True).order_by('order')

    def get_roles_by_app(self, app_uuid):
        applicationrole_ids = self.get_applicationrole_ids()
        return Role.objects.distinct().filter(
            applicationrole__in=applicationrole_ids,
            applicationrole__application__uuid=app_uuid)

    def get_group_and_role_permissions(self):
        """
        get all permissions the user has through his groups and roles
        """
        applicationrole_ids = self.get_applicationrole_ids()
        q = Q(group__role__applicationrole__in=applicationrole_ids,
              group__role__applicationrole__application__uuid=settings.
              SSO_APP_UUID) | Q(group__user=self)
        return Permission.objects.distinct().filter(q)

    @memoize
    def get_applicationrole_ids(self):
        return get_applicationrole_ids(self.id)

    @memoize
    def get_applicationroles(self):
        applicationrole_ids = self.get_applicationrole_ids()
        return ApplicationRole.objects.filter(
            id__in=applicationrole_ids).select_related()

    @memoize
    def get_administrable_application_roles(self):
        """
        get a queryset for the admin
        """
        if self.is_superuser:
            return ApplicationRole.objects.all().select_related()
        else:
            applicationrole_ids = self.get_applicationrole_ids()
            # all roles the user has, with adequate inheritable flag
            if self.is_global_user_admin:
                application_roles = ApplicationRole.objects.filter(
                    id__in=applicationrole_ids,
                    is_inheritable_by_global_admin=True).select_related()
            elif self.is_user_admin:
                application_roles = ApplicationRole.objects.filter(
                    id__in=applicationrole_ids,
                    is_inheritable_by_org_admin=True).select_related()
            else:
                application_roles = ApplicationRole.objects.none()
            return application_roles

    @memoize
    def get_administrable_role_profiles(self):
        if self.is_superuser:
            return RoleProfile.objects.all().prefetch_related(
                'application_roles', 'application_roles__role',
                'application_roles__application')
        else:
            # all role profiles the user has, with adequate inheritable flag
            if self.is_global_user_admin:
                role_profiles = self.role_profiles.filter(
                    is_inheritable_by_global_admin=True)
            elif self.is_user_admin:
                role_profiles = self.role_profiles.filter(
                    is_inheritable_by_org_admin=True)
            else:
                role_profiles = self.role_profiles.none()
            return role_profiles.prefetch_related(
                'application_roles', 'application_roles__role',
                'application_roles__application').distinct()

    @memoize
    def get_administrable_app_admin_application_roles(self):
        """
        get a queryset for the admin
        """
        if self.is_app_user_admin():
            return ApplicationRole.objects.filter(
                application__applicationadmin__admin=self)
        else:
            return ApplicationRole.objects.none()

    @memoize
    def get_administrable_app_admin_role_profiles(self):
        # all role profiles the user has, with adequate inheritable flag
        role_profiles = self.role_profiles.none()
        if self.is_app_user_admin():
            role_profiles = RoleProfile.objects.filter(
                roleprofileadmin__admin=self)

        return role_profiles.prefetch_related(
            'application_roles', 'application_roles__role',
            'application_roles__application').distinct()

    @memoize
    def get_administrable_user_organisations(self):
        """
        return a list of organisations from all the users we have admin rights on
        """
        if self.is_global_user_admin:
            return Organisation.objects.all().select_related(
                'organisation_country__country', 'email', 'association')
        elif self.is_user_admin:
            return Organisation.objects.filter(
                Q(pk__in=self.organisations.all()) |
                Q(admin_region__in=self.admin_regions.all()) |
                Q(organisation_country__in=self.admin_organisation_countries.all()) |
                Q(association__in=self.admin_associations.all())) \
                .select_related('organisation_country__country', 'email', 'association').distinct()
        else:
            return Organisation.objects.none()

    @memoize
    def get_administrable_user_regions(self):
        """
        return a list of regions from all the users we have admin rights on
        """
        if self.is_global_user_admin:
            return AdminRegion.objects.all()
        elif self.is_user_admin:
            return AdminRegion.objects.filter(
                Q(organisation__in=self.organisations.all()) |
                Q(pk__in=self.admin_regions.all()) |
                Q(organisation_country__in=self.admin_organisation_countries.all()) |
                Q(organisation_country__association__in=self.admin_associations.all())) \
                .distinct()
        else:
            return AdminRegion.objects.none()

    @memoize
    def get_administrable_user_countries(self):
        """
        return a list of countries from all the users we have admin rights on
        """
        if self.is_global_user_admin:
            return OrganisationCountry.objects.filter(
                is_active=True).distinct().select_related(
                    'country', 'association')
        elif self.is_user_admin:
            return OrganisationCountry.objects.filter(
                # for adminregions without a associated country
                Q(organisation__admin_region__in=self.admin_regions.all()) |
                Q(organisation__in=self.organisations.all()) |
                Q(adminregion__in=self.admin_regions.all()) |
                Q(pk__in=self.admin_organisation_countries.all()) |
                Q(association__in=self.admin_associations.all())) \
                .select_related('country', 'association').distinct()
        else:
            return OrganisationCountry.objects.none()

    @memoize
    def get_administrable_user_associations(self):
        """
        return a list of associations from all the users we have admin rights on
        """
        if self.is_global_user_admin:
            return Association.objects.all()
        else:
            return self.admin_associations.all()

    @memoize
    def get_administrable_app_admin_user_countries(self):
        """
        return a list of countries from all the users we have app admin rights on
        """
        if self.is_global_app_user_admin or self.is_superuser:
            return OrganisationCountry.objects.all().select_related(
                'country', 'association')
        elif self.is_app_user_admin():
            return OrganisationCountry.objects.filter(
                # for admin regions without a associated country
                Q(organisation__admin_region__in=self.app_admin_regions.all()) |
                Q(organisation__in=self.organisations.all()) |
                Q(adminregion__in=self.app_admin_regions.all()) |
                Q(pk__in=self.app_admin_organisation_countries.all()) |
                Q(association__in=self.app_admin_associations.all())). \
                select_related('country', 'association').distinct()
        else:
            return OrganisationCountry.objects.none()

    @memoize
    def get_administrable_app_admin_user_organisations(self):
        """
        return a list of organisations from all the users we have rights to manage app_roles
        """
        if self.is_global_app_user_admin or self.is_superuser:
            return Organisation.objects.all().select_related(
                'organisation_country__country', 'email')
        elif self.is_app_user_admin():
            return Organisation.objects.filter(
                Q(pk__in=self.organisations.all()) |
                Q(admin_region__in=self.app_admin_regions.all()) |
                Q(organisation_country__in=self.app_admin_organisation_countries.all()) |
                Q(association__in=self.app_admin_associations.all())) \
                .select_related('organisation_country__country', 'email').distinct()
        else:
            return Organisation.objects.none()

    @memoize
    def get_administrable_app_admin_user_regions(self):
        """
        return a list of regions from all the users we have admin rights on
        """
        if self.is_global_app_user_admin or self.is_superuser:
            return AdminRegion.objects.all()
        elif self.is_app_user_admin():
            return AdminRegion.objects.filter(
                Q(organisation__in=self.organisations.all()) |
                Q(pk__in=self.app_admin_regions.all()) |
                Q(organisation_country__in=self.app_admin_organisation_countries.all()) |
                Q(organisation_country__association__in=self.app_admin_associations.all())) \
                .distinct()
        else:
            return AdminRegion.objects.none()

    @memoize
    def get_administrable_organisations(self):
        """
        return a list of all organisations the user has admin rights on
        """
        if self.has_perms([
                "organisations.change_organisation",
                "organisations.access_all_organisations"
        ]):
            return Organisation.objects.filter(
                association__is_external=False).prefetch_related(
                    'organisation_country__country', 'email',
                    'organisationpicture_set')
        elif self.has_perm("organisations.change_organisation"):
            orgs = Organisation.objects.filter(
                Q(association__is_external=False)
                & (Q(user=self) | Q(admin_region__user=self)
                   | Q(organisation_country__user=self)
                   | Q(association__user=self))).distinct()
            return Organisation.objects.filter(id__in=orgs).prefetch_related(
                'organisation_country__country', 'email',
                'organisationpicture_set')
        else:
            return Organisation.objects.none()

    @memoize
    def administrable_organisations_exists(self):
        """
        return if the user has admin rights on organisations
        """
        if self.has_perms([
                "organisations.change_organisation",
                "organisations.access_all_organisations"
        ]):
            return Organisation.objects.all().exists()
        elif self.has_perm("organisations.change_organisation"):
            return Organisation.objects.filter(
                Q(association__is_external=False)
                & (Q(user=self) | Q(admin_region__user=self)
                   | Q(organisation_country__user=self)
                   | Q(association__user=self))).exists()
        else:
            return False

    @memoize
    def get_assignable_associations(self):
        """
        return a list of Associations the user can assign to organisations
        """
        if self.has_perms([
                "organisations.change_organisation",
                "organisations.access_all_organisations"
        ]):
            return Association.objects.filter(is_active=True,
                                              is_external=False).distinct()
        elif self.has_perm("organisations.change_organisation"):
            return Association.objects.filter(user=self,
                                              is_active=True,
                                              is_external=False)
        else:
            return Association.objects.none()

    @memoize
    def get_assignable_organisation_countries(self):
        """
        return a list of OrganisationCountry the user can assign to organisations
        """
        if self.has_perms([
                "organisations.change_organisation",
                "organisations.access_all_organisations"
        ]):
            return OrganisationCountry.objects.filter(
                is_active=True, association__is_active=True, association__is_external=False) \
                .distinct().prefetch_related('country', 'association')
        elif self.has_perm("organisations.change_organisation"):
            return OrganisationCountry.objects.filter(
                Q(is_active=True,
                  association__is_active=True,
                  association__is_external=False)
                & (Q(user=self) | Q(association__user=self))).prefetch_related(
                    'country', 'association')
        else:
            return OrganisationCountry.objects.none()

    @memoize
    def get_assignable_organisation_regions(self):
        """
        return a list of regions the user can assign to organisations
        """
        if self.has_perms([
                "organisations.change_organisation",
                "organisations.access_all_organisations"
        ]):
            return AdminRegion.active_objects.filter(
                is_active=True,
                organisation_country__is_active=True,
                organisation_country__association__is_active=True,
                organisation_country__association__is_external=False,
            )
        elif self.has_perm("organisations.change_organisation"):
            return AdminRegion.active_objects.filter(
                Q(is_active=True,
                  organisation_country__is_active=True,
                  organisation_country__association__is_active=True,
                  organisation_country__association__is_external=False)
                & (Q(user=self) | Q(organisation_country__user=self) | Q(
                    organisation_country__association__user=self))).distinct()
        else:
            return AdminRegion.objects.none()

    @memoize
    def get_administrable_regions(self):
        """
        return a list of all admin_regions the user has admin rights on
        """
        if self.has_perms([
                "organisations.change_adminregion",
                "organisations.access_all_organisations"
        ]):
            return AdminRegion.objects.filter(
                organisation_country__association__is_external=False)
        elif self.has_perm("organisations.change_adminregion"):
            return AdminRegion.objects.filter(
                Q(organisation_country__association__is_external=False)
                & (Q(user=self) | Q(organisation_country__user=self) | Q(
                    organisation_country__association__user=self))).distinct()
        else:
            return AdminRegion.objects.none()

    @memoize
    def get_administrable_region_countries(self):
        """
        return a list of countries from the administrable regions the user has
        """
        if self.has_perms([
                "organisations.change_adminregion",
                "organisations.access_all_organisations"
        ]):
            return OrganisationCountry.objects.filter(association__is_external=False) \
                .distinct().prefetch_related('country', 'association')
        elif self.has_perm("organisations.change_adminregion"):
            return OrganisationCountry.objects.filter(
                Q(association__is_external=False)
                & (Q(adminregion__user=self) | Q(user=self)
                   | Q(association__user=self))).distinct().prefetch_related(
                       'country', 'association')
        else:
            return OrganisationCountry.objects.none()

    @memoize
    def get_administrable_countries(self):
        """
        return a list of countries the user has admin rights on
        """
        if self.has_perms([
                "organisations.change_organisationcountry",
                "organisations.access_all_organisations"
        ]):
            return OrganisationCountry.objects.filter(association__is_external=False) \
                .distinct().prefetch_related('country', 'association')
        elif self.has_perm("organisations.change_organisationcountry"):
            return OrganisationCountry.objects.filter(
                Q(association__is_external=False)
                & (Q(user=self) | Q(association__user=self))).prefetch_related(
                    'country', 'association')
        else:
            return OrganisationCountry.objects.none()

    @memoize
    def get_administrable_associations(self):
        """
        return a list of associations the user has admin rights on
        """
        if self.is_superuser:
            return Association.objects.all()
        elif self.has_perm("organisations.change_association"):
            return Association.objects.filter(user=self)
        else:
            return Association.objects.none()

    @memoize
    def get_count_of_registrationprofiles(self):
        qs = RegistrationProfile.objects.get_not_expired().filter(
            is_access_denied=False,
            user__is_active=False,
            is_validated=True,
            check_back=False,
            user__last_login__isnull=True)
        return RegistrationProfile.objects.filter_administrable_registrationprofiles(
            self, qs).count()

    @memoize
    def get_count_of_organisationchanges(self):
        organisationchanges = OrganisationChange.open.all()
        return self.filter_administrable_organisationchanges(
            organisationchanges).count()

    @memoize
    def get_count_of_extend_access(self):
        access_requests = AccessRequest.open.all()
        return self.filter_administrable_access_requests(
            access_requests).count()

    def filter_administrable_access_requests(self, qs):
        #  filter the access_request for who the authenticated user has access to
        if self.is_superuser:
            pass
        elif self.is_global_user_admin:
            qs = qs.filter(user__is_superuser=False)
        elif self.is_user_admin:
            organisations = self.get_administrable_user_organisations()
            q = Q(user__is_superuser=False) & Q(user__is_service=False)
            q &= (Q(user__organisations__in=organisations)
                  | Q(organisation__in=organisations))
            qs = qs.filter(q).distinct()
        else:
            qs = AccessRequest.objects.none()
        return qs

    def filter_administrable_organisationchanges(self, qs):
        #  filter the organisationchanges for who the authenticated user has access to
        if self.is_superuser:
            pass
        elif self.is_global_user_admin:
            qs = qs.filter(user__is_superuser=False)
        elif self.is_user_admin:
            organisations = self.get_administrable_user_organisations()
            q = Q(user__is_superuser=False) & Q(organisation__in=organisations)
            qs = qs.filter(q).distinct()
        else:
            qs = OrganisationChange.objects.none()
        return qs

    def filter_administrable_users(self, qs):
        # filter the users for who the authenticated user has admin rights
        if self.is_superuser:
            pass
        elif self.is_global_user_admin:
            qs = qs.filter(is_superuser=False, is_service=False)
        elif self.is_user_admin:
            organisations = self.get_administrable_user_organisations()
            q = Q(is_superuser=False) & Q(is_service=False) & Q(
                organisations__in=organisations)
            qs = qs.filter(q).distinct()
        else:
            qs = User.objects.none()
        return qs

    def filter_administrable_user_emails(self, qs):
        # filter the users for who the authenticated user has admin rights
        if self.is_superuser:
            pass
        elif self.is_global_user_admin:
            qs = qs.filter(user__is_superuser=False, user__is_service=False)
        elif self.is_user_admin:
            organisations = self.get_administrable_user_organisations()
            q = Q(user__is_superuser=False) & Q(user__is_service=False) & Q(
                user__organisations__in=organisations)
            qs = qs.filter(q).distinct()
        else:
            qs = qs.none()
        return qs

    def filter_administrable_app_admin_users(self, qs):
        # filter the users for who the authenticated user can manage app_roles
        if self.is_global_app_user_admin:
            qs = qs.filter(is_superuser=False, is_service=False)
        elif self.is_app_user_admin():
            organisations = self.get_administrable_app_admin_user_organisations(
            )
            q = Q(is_superuser=False) & Q(is_service=False) & Q(
                organisations__in=organisations)
            qs = qs.filter(q).distinct()
        else:
            qs = User.objects.none()
        return qs

    def filter_administrable_apps(self, qs):
        # filter the apps for who the authenticated user has admin rights
        if self.is_superuser:
            pass
        elif self.is_global_app_admin:
            pass
        elif self.has_perms(["accounts.view_application"]):
            qs = qs.filter(applicationadmin__admin=self)
        else:
            qs = qs.none()
        return qs

    @property
    def is_guest(self):
        # iterate over all profiles does not makes a new DB query
        # when prefetch_related('role_profiles') is used
        # otherwise self.role_profiles.filter(uuid=settings.SSO_DEFAULT_MEMBER_PROFILE_UUID).exists()
        # would be better
        for profile in self.role_profiles.all():
            if profile.uuid != settings.SSO_DEFAULT_GUEST_PROFILE_UUID:
                return False
        for _ in self.application_roles.all():
            return False
        return True

    @property
    def is_global_user_admin(self):
        # can access user data: name, email, center and roles for all users
        return self.is_user_admin and self.has_perm(
            "accounts.access_all_users")

    @property
    def is_user_admin(self):
        # can access user data: name, email, center and roles
        return self.has_perms([
            "accounts.read_user"
        ])  # is used also by the api for read_only access

    @property
    def is_global_app_user_admin(self):
        return self.is_app_user_admin() and self.has_perm(
            "accounts.app_admin_access_all_users")

    @memoize
    def is_app_user_admin(self):
        return self.applicationadmin_set.exists(
        ) or self.roleprofileadmin_set.exists()

    @property
    def is_global_app_admin(self):
        return self.has_perms(
            ["accounts.view_application", "accounts.access_all_applications"])

    @memoize
    def is_app_admin(self):
        return self.applicationadmin_set.exists() and self.has_perms(
            ["accounts.view_application"])

    @property
    def is_global_organisation_admin(self):
        return self.is_organisation_admin and self.has_perms(
            ["organisations.access_all_organisations"])

    @property
    def is_organisation_admin(self):
        return self.has_perm("organisations.change_organisation")

    @memoize
    def has_organisation(self, uuid):
        return Organisation.objects.filter(
            Q(uuid=uuid, association__is_external=False)
            & (Q(user=self) | Q(admin_region__user=self)
               | Q(organisation_country__user=self)
               | Q(association__user=self))).exists()

    @memoize
    def has_region(self, uuid):
        return AdminRegion.objects.filter(
            Q(uuid=uuid, organisation_country__association__is_external=False)
            & (Q(user=self) | Q(organisation_country__user=self)
               | Q(organisation_country__association__user=self))).exists()

    @memoize
    def has_country(self, uuid):
        return OrganisationCountry.objects.filter(
            Q(uuid=uuid, association__is_external=False)
            & (Q(user=self) | Q(association__user=self))).exists()

    def has_user_access_and_perm(self, uuid, perm):
        """
        Check if the user is an admin of the user with uuid and has the permission
        """
        return self.has_perm(perm) and self.has_user_access(uuid)

    def has_user_access(self, uuid):
        """
        Check if the user is an admin of the user with uuid
        """
        if self.is_superuser:
            return True
        elif self.has_perm("accounts.access_all_users"):
            user = User.objects.get(uuid=uuid)
            return not user.is_superuser and not user.is_service
        else:
            return User.objects.filter(
                Q(uuid=uuid, is_superuser=False, is_service=False)
                & (Q(organisations__user=self)
                   | Q(organisations__admin_region__user=self)
                   | Q(organisations__organisation_country__user=self)
                   | Q(organisations__association__user=self))).exists()

    def has_access_request_access(self, access_request):
        """
        Check if the user is an admin of organisation the user with uuid
        or if the user is an admin of the organisation in the AccessRequest
        """
        if access_request.organisation:
            return self.has_organisation_user_access(
                access_request.organisation.uuid)
        else:
            return self.has_user_access(access_request.user.uuid)

    def has_app_admin_user_access(self, uuid):
        """
        Check if the user is an admin of the user with uuid
        """
        if self.is_superuser:
            return True
        if self.is_global_app_user_admin:
            user = User.objects.get(uuid=uuid)
            return not user.is_superuser and not user.is_service
        else:
            return User.objects.filter(
                Q(uuid=uuid, is_superuser=False, is_service=False)
                &
                (Q(organisations__user=self)
                 | Q(organisations__admin_region__app_admin_user=self)
                 | Q(organisations__organisation_country__app_admin_user=self)
                 | Q(organisations__association__app_admin_user=self))).exists(
                 )

    def has_organisation_user_access(self, uuid):
        # used in sso_xxx_theme
        if self.has_perm("accounts.access_all_users"):
            return True
        else:
            return self.has_organisation(uuid)

    def has_organisation_access_and_perm(self, uuid, perm):
        return self.has_perm(perm) and self.has_organisation_access(uuid)

    def has_organisation_access(self, uuid):
        if self.has_perm("organisations.access_all_organisations"):
            return Organisation.objects.filter(
                uuid=uuid, association__is_external=False).exists()
        else:
            return self.has_organisation(uuid)

    def has_region_access(self, uuid):
        if self.has_perm("organisations.access_all_organisations"):
            return AdminRegion.objects.filter(
                uuid=uuid,
                organisation_country__association__is_external=False).exists()
        else:
            return self.has_region(uuid)

    def has_country_access(self, uuid):
        if self.has_perm("organisations.access_all_organisations"):
            return OrganisationCountry.objects.filter(
                uuid=uuid, association__is_external=False).exists()
        else:
            return self.has_country(uuid)

    @property
    def is_groupemail_admin(self):
        if self.has_perm('emails.change_groupemail'
                         ) or GroupEmailManager.objects.filter(
                             manager=self).exists():
            return True
        else:
            return False

    def has_groupemail_access(self, uuid):
        if self.has_perm('emails.change_groupemail'
                         ) or GroupEmailManager.objects.filter(
                             group_email__uuid=uuid, manager=self).exists():
            return True
        else:
            return False

    def has_app_access(self, uuid):
        if self.is_global_app_admin:
            return True
        else:
            return ApplicationAdmin.objects.filter(application__uuid=uuid,
                                                   admin=self).exists()

    @property
    def is_complete(self):
        if self.first_name and self.last_name:
            return True
        else:
            return False

    @property
    def is_verified(self):
        if hasattr(self, 'otp_device'):
            return self.otp_device is not None
        return False

    def add_default_roles(self):
        app_roles = []
        role_profiles = [self.get_default_role_profile()]

        # enable brand specific modification
        default_roles.send_robust(sender=self.__class__,
                                  user=self,
                                  app_roles=app_roles,
                                  role_profiles=role_profiles)

        self.add_roles(app_roles)

        for role_profile in role_profiles:
            self.role_profiles.add(role_profile)
        self.update_last_modified()

    def add_roles(self, app_roles_dict_array):
        # get or create Roles
        for app_roles_dict_item in app_roles_dict_array:
            roles = []
            for roles_name in app_roles_dict_item['roles']:
                roles += [Role.objects.get_or_create(name=roles_name)[0]]
            app_roles_dict_item['roles'] = roles

        for app_roles_dict_item in app_roles_dict_array:
            try:
                application = Application.objects.get(
                    uuid=app_roles_dict_item['uuid'])
                app_roles = []
                for role in app_roles_dict_item['roles']:
                    app_roles += [
                        ApplicationRole.objects.get_or_create(
                            application=application, role=role)[0]
                    ]
                self.application_roles.add(*app_roles)
            except ObjectDoesNotExist:
                logger.warning("Application %s does not exist" %
                               app_roles_dict_item['uuid'])
            except IntegrityError as e:
                # programming error?
                logger.exception(e)

    def set_organisations(self, organisations):
        # Ensure last_modified will be updated in all cases the user changes the organisation
        # Force evaluation of `organisations` in case it's a queryset whose value
        # could be affected by `manager.clear()`. Refs #19816.
        organisations = tuple(organisations)
        self.organisations.clear()
        self.organisations.through.objects.bulk_create([
            self.organisations.through(**{
                'user_id': self.id,
                'organisation_id': organisation.id,
            }) for organisation in organisations
        ])
        ensure_single_primary(
            self.organisations.through.objects.filter(user_id=self.id))

    def add_organisation(self, organisation, primary=False):
        # Ensure last_modified will be updated in all cases the user changes the organisation
        self.organisations.through.objects.create(
            **{
                'user_id': self.id,
                'organisation_id': organisation.id,
                'primary': primary
            })
        ensure_single_primary(
            self.organisations.through.objects.filter(user_id=self.id))

    def remove_organisation_related_permissions(
            self,
            organisation_related_application_roles=None,
            organisation_related_role_profiles=None):
        if organisation_related_application_roles is None:
            organisation_related_application_roles = ApplicationRole.objects.filter(
                is_organisation_related=True)
        if organisation_related_role_profiles is None:
            organisation_related_role_profiles = RoleProfile.objects.filter(
                is_organisation_related=True)
        self.application_roles.remove(
            *list(organisation_related_application_roles))
        self.role_profiles.remove(*list(organisation_related_role_profiles))
Example #19
0
class Profile(models.Model):

    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                on_delete=models.CASCADE)
    slug = sluggable_fields.SluggableField(decider=ProfileSlug,
                                           populate_from="name",
                                           slugify=slugify_user,
                                           unique=True)
    is_public = models.BooleanField(default=True)
    is_approved = models.BooleanField(default=False)
    name = models.CharField(max_length=200,
                            validators=[validate_sluggable_name])
    image = thumbnail.ImageField(
        upload_to=upload_path.auto_cleaned_path_stripped_uuid4, blank=True)
    city_or_town = models.CharField(max_length=100, blank=True)
    country = models.CharField(max_length=100, blank=True)
    linkedin_url = models.URLField(max_length=400, blank=True)
    facebook_url = models.URLField(max_length=400, blank=True)
    personal_website_url = models.URLField(max_length=400, blank=True)
    lat = models.FloatField(null=True, blank=True, default=None)
    lon = models.FloatField(null=True, blank=True, default=None)
    cause_areas = postgres_fields.ArrayField(enum.EnumField(CauseArea),
                                             blank=True,
                                             default=list)
    cause_areas_other = models.TextField(blank=True,
                                         validators=[MaxLengthValidator(2000)])
    available_to_volunteer = models.BooleanField(null=True,
                                                 blank=True,
                                                 default=None)
    open_to_job_offers = models.BooleanField(null=True,
                                             blank=True,
                                             default=None)
    expertise_areas = postgres_fields.ArrayField(enum.EnumField(ExpertiseArea),
                                                 blank=True,
                                                 default=list)
    expertise_areas_other = models.TextField(
        blank=True, validators=[MaxLengthValidator(2000)])
    career_interest_areas = postgres_fields.ArrayField(
        enum.EnumField(ExpertiseArea), blank=True, default=list)
    available_as_speaker = models.BooleanField(null=True,
                                               blank=True,
                                               default=None)
    email_visible = models.BooleanField(default=False)
    topics_i_speak_about = models.TextField(
        blank=True, validators=[MaxLengthValidator(2000)])
    organisational_affiliations = postgres_fields.ArrayField(
        enum.EnumField(OrganisationalAffiliation), blank=True, default=list)
    summary = models.TextField(blank=True,
                               validators=[MaxLengthValidator(2000)])
    giving_pledges = postgres_fields.ArrayField(enum.EnumField(GivingPledge),
                                                blank=True,
                                                default=list)
    local_groups = models.ManyToManyField(LocalGroup,
                                          through="Membership",
                                          blank=True)
    legacy_record = models.PositiveIntegerField(null=True,
                                                default=None,
                                                editable=False,
                                                unique=True)
    offering = models.TextField(blank=True,
                                validators=[MaxLengthValidator(2000)])
    looking_for = models.TextField(blank=True,
                                   validators=[MaxLengthValidator(2000)])

    slugs = contenttypes_fields.GenericRelation(ProfileSlug)

    objects = ProfileManager()

    class Meta:
        ordering = ["name", "slug"]

    def __str__(self):
        return self.name

    def is_searchable(self) -> bool:
        return self.is_approved and self.is_public and self.user.is_active

    def get_absolute_url(self):
        return urls.reverse("profile", args=[self.slug])

    def get_email_searchable(self) -> Optional[str]:
        return self.user.email if self.email_visible else None

    def geocode(self):
        self.lat = None
        self.lon = None
        if self.city_or_town and self.country:
            geocoders.options.default_user_agent = "eahub"
            location = geocoders.Nominatim(
                timeout=10).geocode(f"{self.city_or_town}, {self.country}")
            if location:
                self.lat = location.latitude
                self.lon = location.longitude
        return self

    def get_pretty_cause_areas(self):
        return prettify_property_list(CauseArea, self.cause_areas,
                                      self.cause_areas_other)

    def get_image_url(self) -> Optional[str]:
        if self.image:
            return get_thumbnail(self.image, "200x200", crop="center").url
        else:
            return None

    # todo rename to get_list something
    def get_cause_areas_searchable(self) -> List[str]:
        return self._format_enum_array_for_searching(self.cause_areas,
                                                     CauseArea)

    def get_pretty_expertise(self):
        return prettify_property_list(ExpertiseArea, self.expertise_areas,
                                      self.expertise_areas_other)

    def get_expertise_searchable(self) -> List[str]:
        return self._format_enum_array_for_searching(self.expertise_areas,
                                                     ExpertiseArea)

    def get_pretty_career_interest_areas(self):
        return prettify_property_list(ExpertiseArea,
                                      self.career_interest_areas)

    def get_career_interest_areas_searchable(self) -> List[str]:
        return self._format_enum_array_for_searching(
            self.career_interest_areas, ExpertiseArea)

    def get_pretty_giving_pledges(self):
        if self.giving_pledges:
            return ", ".join(map(GivingPledge.label, self.giving_pledges))
        else:
            return "N/A"

    def get_giving_pledges_searchable(self) -> List[str]:
        return self._format_enum_array_for_searching(self.giving_pledges,
                                                     GivingPledge)

    def get_pretty_organisational_affiliations(self):
        if self.organisational_affiliations:
            return ", ".join(
                map(OrganisationalAffiliation.label,
                    self.organisational_affiliations))
        else:
            return "N/A"

    def get_organisational_affiliations_searchable(self) -> List[str]:
        return self._format_enum_array_for_searching(
            self.organisational_affiliations, OrganisationalAffiliation)

    def get_pretty_local_groups(self):
        if self.local_groups:
            return ", ".join(self.get_local_groups_searchable())
        else:
            return "N/A"

    def get_local_groups_searchable(self) -> List[str]:
        return [f"{group.name}" for group in self.local_groups.all()]

    def get_organizer_of_local_groups_searchable(self) -> List[str]:
        return [f"{group.name}" for group in self.user.localgroup_set.all()]

    def write_data_export_zip(self, request, response):
        with zipfile.ZipFile(response, mode="w") as zip_file:
            with zip_file.open(f"{self.slug}.json",
                               mode="w") as json_binary_file, io.TextIOWrapper(
                                   json_binary_file) as json_file:
                json.dump(
                    {
                        "email":
                        self.user.email,
                        "date_joined":
                        self.user.date_joined.isoformat(),
                        "last_login":
                        self.user.last_login.isoformat(),
                        "url":
                        request.build_absolute_uri(self.get_absolute_url()),
                        "is_public":
                        self.is_public,
                        "is_approved":
                        self.is_approved,
                        "name":
                        self.name,
                        "city_or_town":
                        self.city_or_town,
                        "country":
                        self.country,
                        "cause_areas":
                        list(map(CauseArea.label, self.cause_areas)),
                        "cause_areas_other":
                        self.cause_areas_other,
                        "available_to_volunteer":
                        self.available_to_volunteer,
                        "open_to_job_offers":
                        self.open_to_job_offers,
                        "expertise_areas":
                        list(map(ExpertiseArea.label, self.expertise_areas)),
                        "expertise_areas_other":
                        self.expertise_areas_other,
                        "career_interest_areas":
                        list(
                            map(ExpertiseArea.label,
                                self.career_interest_areas)),
                        "available_as_speaker":
                        self.available_as_speaker,
                        "topics_i_speak_about":
                        self.topics_i_speak_about,
                        "organisational_affiliations":
                        list(
                            map(
                                OrganisationalAffiliation.label,
                                self.organisational_affiliations,
                            )),
                        "summary":
                        self.summary,
                        "giving_pledges":
                        list(map(GivingPledge.label, self.giving_pledges)),
                        "member_of_local_groups": [
                            request.build_absolute_uri(
                                local_group.get_absolute_url())
                            for local_group in self.local_groups.all()
                        ],
                        "organiser_of_local_groups": [
                            request.build_absolute_uri(
                                local_group.get_absolute_url())
                            for local_group in self.user.localgroup_set.all()
                        ],
                        "aliases": [
                            request.build_absolute_uri(
                                urls.reverse("profile",
                                             kwargs={"slug": slug.slug}))
                            for slug in self.slugs.filter(redirect=True)
                        ],
                        "legacy_hub_url":
                        (self.legacy_record and request.build_absolute_uri(
                            urls.reverse(
                                "profile_legacy",
                                kwargs={"legacy_record": self.legacy_record},
                            ))),
                    },
                    json_file,
                    indent=2,
                )
            if self.image:
                with self.image.open() as image_src_file, zip_file.open(
                        self.slug + pathlib.PurePath(self.image.name).suffix,
                        mode="w") as image_dst_file:
                    shutil.copyfileobj(image_src_file, image_dst_file)

    def image_placeholder(self):
        return f"Avatar{self.id % 10}.jpg"

    def has_cause_area_details(self):
        cause_area_details_exist = [
            len(self.cause_areas) > 0,
            len(self.cause_areas_other) > 0,
            len(self.giving_pledges) > 0,
            self.available_to_volunteer,
        ]
        return any(cause_area_details_exist)

    def has_career_details(self):
        career_details_exist = [
            len(self.expertise_areas),
            len(self.expertise_areas_other),
            self.open_to_job_offers,
        ]
        return any(career_details_exist)

    def has_community_details(self):
        community_details_exist = [
            len(self.organisational_affiliations) > 0,
            self.local_groups.exists(),
            self.user.localgroup_set.exists(), self.available_as_speaker,
            len(self.topics_i_speak_about) > 0, self.offering, self.looking_for
        ]
        return any(community_details_exist)

    def get_is_organiser(self):
        return self.user.localgroup_set.exists()

    def convert_to_row(self, field_names):
        values = []
        for field in field_names:
            if "_other" in field:
                continue
            elif field == "cause_areas":
                values.append(self.get_pretty_cause_areas())
            elif field == "expertise_areas":
                values.append(self.get_pretty_expertise())
            elif field == "career_interest_areas":
                values.append(self.get_pretty_career_interest_areas())
            elif field == "organisational_affiliations":
                values.append(self.get_pretty_organisational_affiliations())
            elif field == "giving_pledges":
                values.append(self.get_pretty_giving_pledges())
            elif field == "local_groups":
                values.append(self.get_pretty_local_groups())
            else:
                values.append(getattr(self, field))
        return values

    def _format_enum_array_for_searching(self, enum_values_list: List[Union[
        enum.Enum, str, int]], enum_cls: enum.Enum) -> List[str]:
        enum_labels: List[str] = []
        for enum_value_raw in enum_values_list:
            enum_value = int(enum_value_raw)
            enum_labels.append(enum_cls.values[enum_value].label)
        return enum_labels

    @staticmethod
    def get_exportable_field_names():
        return [
            field.name
            for field in Profile._meta.fields + Profile._meta.many_to_many
            if "_other" not in field.name
        ]
Example #20
0
class PrintIssue(models.Model, Edit_url_mixin):
    """ PDF file of a printed newspaper. """
    class Meta:
        # ordering = ['-publication_date']
        verbose_name = _('Pdf issue')
        verbose_name_plural = _('Pdf issues')

    issue = models.ForeignKey(Issue, related_name='pdfs')

    pages = models.IntegerField(
        help_text='Number of pages',
        editable=False,
    )

    pdf = models.FileField(
        help_text=_('Pdf file for this issue.'),
        upload_to=upload_pdf_to,
    )

    cover_page = thumbnail.ImageField(
        help_text=_('An image file of the front page'),
        upload_to='pdf/covers/',
        blank=True,
        null=True,
    )

    text = models.TextField(
        help_text=_('Extracted from file.'),
        editable=False,
    )

    def __str__(self):
        if self.pdf:
            return self.pdf.url
        else:
            return super().__str__()

    def save(self, *args, **kwargs):
        if self.pk:
            old_self = type(self).objects.get(pk=self.pk)
        else:
            old_self = PrintIssue()
        if self.pdf and old_self.pdf != self.pdf:
            reader = PyPDF2.PdfFileReader(self.pdf, strict=False)
            self.pages = reader.numPages
            self.text = reader.getPage(0).extractText()[:200]
            self.cover_page.delete()
        if not self.pdf and self.cover_page:
            self.cover_page.delete()
        if self.pdf and not self.issue_id:
            publication_date = self.get_publication_date()
            self.issue, created = Issue.objects.get_or_create(
                publication_date=publication_date, )

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

    def create_thumbnail(self):
        """ Create a jpg version of the pdf frontpage """
        def pdf_frontpage_to_image():
            reader = PyPDF2.PdfFileReader(self.pdf.file, strict=False)
            writer = PyPDF2.PdfFileWriter()
            first_page = reader.getPage(0)
            writer.addPage(first_page)
            outputStream = BytesIO()
            writer.write(outputStream)
            outputStream.seek(0)
            img = WandImage(
                blob=outputStream,
                format='pdf',
                resolution=60,
            )
            background = WandImage(width=img.width,
                                   height=img.height,
                                   background=Color('white'))
            background.format = 'jpeg'
            background.composite(img, 0, 0)
            return background

        def pdf_not_found():
            """Creates an error frontpage"""
            img = WandImage(
                width=400,
                height=600,
            )
            msg = 'ERROR:\n{}\nnot found on disk'.format(self.pdf.name)
            with Drawing() as draw:
                draw.text_alignment = 'center'
                draw.text(img.width // 2, img.height // 3, msg)
                draw(img)
            background = WandImage(width=img.width,
                                   height=img.height,
                                   background=Color('white'))
            background.format = 'jpeg'
            background.composite(img, 0, 0)
            return background

        filename = pathlib.Path(self.pdf.name).with_suffix('.jpg').name

        try:
            cover = pdf_frontpage_to_image()
        except Exception as err:
            cover = pdf_not_found()
            filename = filename.replace('.jpg', '_not_found.jpg')
            logger.error('Error: %s pdf not found: %s ' % (err, self.pdf))

        blob = BytesIO()
        cover.save(blob)
        imagefile = ContentFile(blob.getvalue())
        self.cover_page.save(filename, imagefile, save=True)

    def get_thumbnail(self):
        """ Get or create a jpg version of the pdf frontpage """
        pdf = self.pdf
        if not pdf:
            return None
        if self.cover_page:
            return self.cover_page

        self.create_thumbnail()
        return self.cover_page

    def get_publication_date(self):
        dateline_regex = (r'^\d(?P<day>\w+) (?P<date>\d{1,2})\.'
                          r' (?P<month>\w+) (?P<year>\d{4})')
        MONTHS = [
            'januar',
            'februar',
            'mars',
            'april',
            'mai',
            'juni',
            'juli',
            'august',
            'september',
            'oktober',
            'november',
            'desember',
        ]
        pdf_data = self.pdf.file.read()
        try:
            page_2_text = extract_pdf_text(pdf_data, 2)
        except subprocess.CalledProcessError:
            page_2_text = ''

        dateline = re.match(dateline_regex, page_2_text)
        if dateline:
            day = int(dateline.group('date'))
            year = int(dateline.group('year'))
            month = MONTHS.index(dateline.group('month')) + 1
            created = datetime.date(day=day, month=month, year=year)
        else:
            # Finds creation date.
            try:
                created = datetime.date.fromtimestamp(
                    os.path.getmtime(self.pdf.path))
            except NotImplementedError:
                key = self.pdf.file.key
                created = boto.utils.parse_ts(key.last_modified)
            # Sets creation date as a Wednesday, if needed.
            created = created + datetime.timedelta(days=3 -
                                                   created.isoweekday())
        return created

    def get_absolute_url(self):
        return self.pdf.url
Example #21
0
class Listing(SlugMixin, AbstractProduct):
    FORCE_SLUG_REGENERATION = False

    SOCIAL_NETWORKS = ['Facebook', 'Twitter', 'Google', 'Instagram', 'Vimeo', 'YouTube', 'LinkedIn', 'Dribbble',
                       'Skype', 'Foursquare', 'Behance']  # TODO: move to settings

    PRICE_UNITS = [
        ('ENTRY', _('entry')),  # tickets
        ('HOUR', _('hour')),  # parking
        ('DAY', _('day')),  # vehicle per day
        ('NIGHT', _('night')),  # room unit per night
    ]

    # definition
    title = models.CharField(_('title'), max_length=100)
    slug = models.SlugField(unique=True, max_length=SlugMixin.MAX_SLUG_LENGTH, blank=True)
    description = models.TextField(_('description'), blank=True)

    # management
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_('author'))
    published = models.BooleanField(_('published'), default=True)
    hidden = models.BooleanField(_('hidden'), default=False)
    promoted = models.BooleanField(_('promoted'), default=False)
    rank = models.PositiveSmallIntegerField(_('rank'), default=1)

    # specification
    categories = models.ManyToManyField(to=Category, verbose_name=_('categories'), blank=True, related_name='listings_of_category')
    features = models.ManyToManyField(to=Feature, verbose_name=_('features'), blank=True, related_name='listings_with_features')

    # price
    price_starts_at = models.BooleanField(_('price starts at'), default=False)
    # price = models.DecimalField(_('price'), help_text=inventor_settings.CURRENCY, max_digits=10, decimal_places=2, db_index=True, validators=[MinValueValidator(0)],
    #                             blank=True, null=True, default=None)
    price_unit = models.CharField(_('price unit'), choices=PRICE_UNITS, max_length=5, blank=True)
    price_per_person = models.BooleanField(_('price per person'), default=False)

    # location
    locality = models.ForeignKey(Locality, on_delete=models.SET_NULL,
                                 blank=True, null=True, default=None)

    address = models.TextField(_('address'), help_text=_('street, postcode, city'), max_length=500, blank=True)

    # street = models.CharField(_('street'), max_length=200, blank=True)
    # postcode = models.CharField(_('postcode'), max_length=30, blank=True)
    # city = models.CharField(_('city'), max_length=50, blank=True)
    country = CountryField(verbose_name=_('country'), blank=True, db_index=True)
    point = models.PointField(_('point'), blank=True, null=True, default=None, db_index=True)

    # previews
    image = thumbnail.ImageField(
        verbose_name=_('image'),
        help_text=_('photo or image'),
        max_length=1024,
        upload_to='images',
        blank=True
    )

    banner = models.ImageField(
        verbose_name=_('banner'),
        help_text=_('photo or image'),
        max_length=1024 * 5,
        upload_to='banners',
        blank=True
    )

    # contact information
    person = models.CharField(_('person'), max_length=100, blank=True)
    phone = models.CharField(_('phone'), max_length=40, blank=True)
    email = models.EmailField(_('email'), blank=True)
    website = models.URLField(_('website'), max_length=400, blank=True)

    # social
    social_networks = HStoreField(verbose_name=_('social networks'), blank=True, default=dict)

    # relations
    # comments = GenericRelation(get_comment_model(), content_type_field='content_type', object_id_field='object_pk',
    #                            related_query_name='comment')

    supplies = GenericRelation('commerce.Supply', content_type_field='content_type', object_id_field='object_id',
                               related_query_name='product')

    created = models.DateTimeField(_('created'), auto_now_add=True)
    modified = models.DateTimeField(_('modified'), auto_now=True)
    i18n = TranslationField(fields=('title', 'slug', 'description'))
    objects = MultilingualManager.from_queryset(ListingQuerySet)()

    class Meta:
        verbose_name = _('listing')
        verbose_name_plural = _('listings')
        ordering = ('title',)
        db_table = 'listings_general'
        indexes = [GinIndex(fields=["i18n"]), ]

    def __str__(self):
        return self.title_i18n

    def get_absolute_url(self):
        return reverse('listings:listing_detail', args=(self.slug_i18n,))

    def get_update_url(self):
        return reverse('listings:listing_update', args=(self.pk,))

    @property
    def full_address(self):
        return '{}, {}'.format(self.address, self.country).strip(', ')

    def get_price_display(self):
        if not self.price:
            return ''

        price = str(self.price)

        if price.endswith('.00'):
            price = price[:-3]

        if inventor_settings.CURRENCY_AFTER_AMOUNT:
            price_display = '{} {}'.format(price, inventor_settings.CURRENCY_SYMBOL)  # example: 10 €
        else:
            price_display = '{}{}'.format(inventor_settings.CURRENCY_SYMBOL, price)   # example: $10

        if self.price_starts_at:
            price_display = '{} {}'.format(_('starts at'), price_display)

        if self.price_unit:
            price_display = '{} / {}'.format(price_display, self.get_price_unit_display())

        return price_display

    @cached_property
    def rating(self):
        avg_rating = self.comments.aggregate(Avg('rating'))
        # TODO: rating subjects:
        # - Facilities / Amenities
        # - Cleanliness
        # - Comfort
        # - Location
        # - Services?
        rating = avg_rating['rating__avg']
        if rating:
            rating = round(rating, 2)
        return rating

    def delete(self, **kwargs):
        """ Deletes file before deleting instance """
        self.delete_banner()
        super().delete(**kwargs)

    def delete_banner(self):
        """ Deletes image file """
        try:
            os.remove(self.banner.path)
        except ValueError:
            pass
        except IOError:
            pass
        except OSError:
            pass

    @property
    def listing_class(self):
        return self.__class__

    @property
    def listing_class_name(self):
        return self.__class__.__name__

    def get_listing_type_display(self):
        # return self.listing_class._meta.verbose_name
        return self.get_real_instance().listing_class._meta.verbose_name

    def get_listing_type_display_plural(self):
        # return self.listing_class._meta.verbose_name_plural
        return self.get_real_instance().listing_class._meta.verbose_name_plural

    @cached_property
    def all_images(self):
        return Photo.objects.filter(album__listing__pk=self.pk)

    @classmethod
    def get_list_url_name(cls):
        url_name = f'{cls.__name__.lower()}_list'
        return f'{url_name}'

    @classmethod
    def get_list_url(cls):
        url_name = cls.get_list_url_name()
        return reverse(f'listings:{url_name}')

    # https://stackoverflow.com/questions/21063078/convert-a-subclass-model-instance-to-another-subclass-model-instance-in-django
    def convert(self, to_type):
        instance = self
        # print(f'[{instance.id}]\t {instance}')

        # create new instance with same parent ID
        new_instance = to_type(listing_ptr_id=instance.id)

        # update parent fields
        new_instance.__dict__.update(instance.__dict__)

        # delete the subclass while keeping the parent
        instance.delete(keep_parents=True)

        # save new instance
        new_instance.save()

    def get_real_instance(self):
        """ get object child instance """

        def get_subclasses(cls):
            subclasses = cls.__subclasses__()
            result = []
            for subclass in subclasses:
                if not subclass._meta.abstract:
                    result.append(subclass)
                else:
                    result += get_subclasses(subclass)
            return result

        if hasattr(self, '_real_instance'):  # try looking in our cache
            return self._real_instance

        subclasses = get_subclasses(self.__class__)

        if not subclasses:  # already real_instance
            self._real_instance = self
            return self._real_instance
        else:
            subclasses_names = [cls.__name__.lower() for cls in subclasses]
            for subcls_name in subclasses_names:
                if hasattr(self, subcls_name):
                    return getattr(self, subcls_name, self)
            return self
Example #22
0
class CarouselImage(models.Model):
    image_file = thumbnail.ImageField(upload_to='carousel')
    title = models.CharField(max_length=200, blank=True, null=True)
    product = models.ForeignKey(Product, blank=True, null=True)
    home_content = models.ForeignKey(HomeContent)
Example #23
0
class Trial(VotableMixin, models.Model):
    """
    An individual trial that we are running.
    """
    ANYONE = 'an'
    INVITATION = 'in'
    RECRUITMENT_CHOICES = ((ANYONE, 'Anyone can join'),
                           (INVITATION, "Only people I've invited can join"))

    IMMEDIATE = 'im'
    HOURS = 'ho'
    DATE = 'da'
    ON_DEMAND = 'de'
    INSTRUCTION_CHOICES = (
        (IMMEDIATE, 'Straight away after randomisation'),
        # (HOURS, 'X hours after randomisation'),
        (DATE, 'On this date...'),
        (ON_DEMAND, 'On Demand'))

    ONCE = 'on'
    WHENEVER = 'wh'
    DATED = 'da'
    REGULARLY = 're'
    OFFLINE = 'of'

    REPORT_STYLE_CHOICES = (
        (ONCE, 'Once only'),
        (WHENEVER, 'Whenever they want'),
        (DATED, 'On date x'),
        # (REGULARLY, 'Regularly')
    )

    DAILY = 'da'
    WEEKLY = 'we'
    MONTHLY = 'mo'

    FREQ_CHOICES = ((DAILY, 'Once per day'), (WEEKLY, 'Once per week'),
                    (MONTHLY, 'Once per month'))

    MANUALLY = 'ma'
    REPORT_NUM = 're'
    ENDING_CHOICES = ((MANUALLY, 'Manually'),
                      (REPORT_NUM, 'After X have reported'), (DATED,
                                                              'On date Y'))

    HELP_PART = """Who can participate in this trial?
(Everyone? People with an infant under 6 months? People who binge drink Alcohol?)"""
    HELP_A = """These are the instructions that will be sent to group A"""
    HELP_B = """These are the instructions that will be sent to group B"""

    # Step 1
    title = models.CharField(max_length=200, blank=True, null=True)

    # Step 2
    reporting_style = models.CharField("Reporting Style",
                                       max_length=2,
                                       choices=REPORT_STYLE_CHOICES,
                                       default=ONCE)
    reporting_freq = models.CharField("Reporting frequency",
                                      max_length=2,
                                      choices=FREQ_CHOICES,
                                      default=DAILY)
    reporting_date = models.DateField('Reporting date', blank=True, null=True)

    # Step 3
    min_participants = models.IntegerField(
        "I think I need a minimum of x participants for a meaningful answer")

    # Step 4
    recruitment = models.CharField(max_length=2,
                                   choices=RECRUITMENT_CHOICES,
                                   default=ANYONE)

    # Step 5
    description = models.TextField(blank=True, null=True)
    image = thumbnail.ImageField(upload_to='uploads', blank=True, null=True)
    secret_info = models.TextField(blank=True, null=True)

    # Step 6
    group_a = models.TextField("Group A Instructions", help_text=HELP_A)
    group_b = models.TextField("Group B Instructions", help_text=HELP_B)
    instruction_delivery = models.CharField(max_length=2,
                                            choices=INSTRUCTION_CHOICES,
                                            default=IMMEDIATE)
    instruction_hours_after = models.IntegerField(blank=True, null=True)
    instruction_date = models.DateField(blank=True, null=True)

    # Step 7
    ending_style = models.CharField(max_length=2,
                                    choices=ENDING_CHOICES,
                                    default=MANUALLY)
    ending_reports = models.IntegerField(blank=True, null=True)
    ending_date = models.DateField(blank=True, null=True)

    # Currently unused power calcs
    group_a_expected = models.IntegerField(blank=True, null=True)
    group_b_impressed = models.IntegerField(blank=True, null=True)

    # popularity
    votes = generic.GenericRelation(Vote)

    # Metadata
    owner = models.ForeignKey(settings.AUTH_USER_MODEL)
    n1trial = models.BooleanField(default=False)
    offline = models.BooleanField(default=False)
    featured = models.BooleanField(default=False)
    stopped = models.BooleanField(default=False)
    is_edited = models.BooleanField(default=False)
    created = models.DateTimeField(default=lambda: datetime.datetime.now())
    private = models.BooleanField(default=False)
    hide = models.NullBooleanField(default=False, blank=True, null=True)
    parent = models.ForeignKey('self',
                               blank=True,
                               null=True,
                               related_name='child')

    # Currently unused advanced user participants
    participants = models.TextField(help_text=HELP_PART, blank=True, null=True)

    objects = managers.RmTrialManager()

    def __unicode__(self):
        """
        Nice printing representation
        """
        return u'#({0}) {1}'.format(self.pk, self.title)

    def get_absolute_url(self):
        return reverse('trial-detail', kwargs={'pk': self.pk})

    def save(self):
        """
        Check for recruiting status

        Return: None
        Exceptions: None
        """
        if self.recruitment == self.INVITATION:
            self.private = True
        return super(Trial, self).save()

    def image_url(self):
        """
        Return the url for SELF.image or None

        Return: str or None
        Exceptions: None
        """
        if not self.image:
            return
        return settings.MEDIA_URL + self.image.file.name.split('/')[-1]

    def results(self):
        """
        Return the results of this trial

        Return: dict
        Exceptions: None
        """
        anal = self.trialanalysis_set.get()
        return [
            dict(name='Group A', avg=anal.meana),
            dict(name='Group B', avg=anal.meanb)
        ]

    @property
    def started(self):
        """
        Property to determine whether this trial has started or not.

        Return: bool
        Exceptions: None
        """
        if self.start_date is None:
            return False
        if self.start_date <= datetime.date.today():
            return True
        return False

    @property
    def finished(self):
        """
        Predicate property to determine whether this trial is finished.

        Return: bool
        Exceptions: None
        """
        if self.stopped:
            return True

        return False

    @property
    def active(self):
        """
        Property to determine whether this trial is active today.

        Return: bool
        Exceptions: None
        """
        if not self.start_date or not self.finish_date:
            return False
        if self.start_date <= td() and self.finish_date >= td():
            return True
        return False

    @property
    def is_invitation_only(self):
        """
        Predicate property to determine whether the trial
        is invitation_only.

        Return: None
        Exceptions: None
        """
        return self.recruitment == self.INVITATION

    def main_outcome(self):
        """
        Return the trial's main outcome
        """
        try:
            return self.variable_set.all()[0]
        except IndexError:
            return []

    def related(self):
        """
        Get trials possibly related to this one.

        Return: Queryset
        Exceptions: None
        """
        return Trial.objects.exclude(pk=self.pk)[:5]

    def time_remaining(self):
        """
        How much time is between now and the end of the trial?

        Return: timedelta or str
        Exceptions: None
        """
        return 'No longer appropriate'

    def can_join(self):
        """
        Predicate method to determine whether users are able to
        join this trial.

        We decide that a trial is unjoinable if it's finish date has
        passed, or if it's max participants limit has been met.
        """
        if self.stopped == True or self.n1trial == True:
            return False
        return True

    def needs(self):
        """
        How many participants does this trial need?

        Return: bool
        Exceptions: None
        """
        return (self.min_participants - self.participant_set.count())

    def needs_participants(self):
        """
        Does this trial need participants?

        Return: bool
        Exceptions: None
        """
        return self.needs() > 0

    def ensure_groups(self):
        """
        Ensure that the groups for this trial exist.
        """
        groupa = Group.objects.get_or_create(trial=self, name=Group.GROUP_A)[0]
        groupb = Group.objects.get_or_create(trial=self, name=Group.GROUP_B)[0]
        return groupa, groupb

    def join(self, user):
        """
        Add a user to our trial.

        Make sure that the trial has groups, then randomly assign USER
        to one of those groups.

        Ensure that this user hasn't already joined the trial, raising
        AlreadyJoinedError if we have.

        Ensure that this trial isn't already finished, raising
        TrialFinishedError if it is.

        If nobody has joined yet, we go to Group A, else Group A if
        the groups are equal, else Group B.
        """
        if self.stopped:
            raise exceptions.TrialFinishedError()
        if Participant.objects.filter(trial=self, user=user).count() > 0:
            raise exceptions.AlreadyJoinedError()
        part = Participant(trial=self, user=user).randomise()
        part.save()
        if self.instruction_delivery == self.IMMEDIATE:
            part.send_instructions()
        if self.instruction_delivery == self.HOURS:
            eta = datetime.utcnow() + datetime.timedelta(
                seconds=60 * 60 * self.instruction_hours_after)
            tasks.instruct_later.apply_async((participant.pk), eta=eta)
        return

    def randomise(self):
        """
        Randomise the participants of this trial.

        If we have already randomised the participants, raise AlreadyRandomisedError.

        Return: None
        Exceptions: AlreadyRandomisedError
        """
        if self.participant_set.filter(group__isnull=False).count() > 0:
            raise exceptions.AlreadyRandomisedError()
        groupa, groupb = self.ensure_groups()
        for participant in self.participant_set.all():
            participant.group = random.choice([groupa, groupb])
            participant.save()
        return

    def send_instructions(self):
        """
        Email the participants of this trial with their instructions.

        Return: None
        Exceptions:
            - TrialFinishedError: The trial has finished
            - TrialNotStartedError: The trial is yet to start
        """
        # if self.start_date is not None and self.start_date > td():
        #     raise exceptions.TrialNotStartedError()
        if self.stopped:
            raise exceptions.TrialFinishedError()
        for participant in self.participant_set.all():
            participant.send_instructions()
        return

    def stop(self):
        """
        Stop this trial please.

        Return: None
        Exceptions: None
        """
        self.stopped = True
        self.save()
        TrialAnalysis.report_on(self)
        if self.offline:
            return
        for participant in self.participant_set.exclude(user=self.owner):
            participant.send_ended_notification()
        return

    def num_reports(self):
        """
        the number of completed reports

        Return: int
        Exceptions: None
        """
        return self.report_set.exclude(date__isnull=True).count()
Example #24
0
class Profile(models.Model):

    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                on_delete=models.CASCADE)
    slug = sluggable_fields.SluggableField(decider=ProfileSlug,
                                           populate_from="name",
                                           slugify=slugify_user,
                                           unique=True)
    is_public = models.BooleanField(default=True)
    name = models.CharField(max_length=200,
                            validators=[validate_sluggable_name])
    image = thumbnail.ImageField(
        upload_to=upload_path.auto_cleaned_path_stripped_uuid4, blank=True)
    city_or_town = models.CharField(max_length=100, blank=True)
    country = models.CharField(max_length=100, blank=True)
    lat = models.FloatField(null=True, blank=True, default=None)
    lon = models.FloatField(null=True, blank=True, default=None)
    cause_areas = postgres_fields.ArrayField(enum.EnumField(CauseArea),
                                             blank=True,
                                             default=list)
    cause_areas_other = models.TextField(blank=True)
    available_to_volunteer = models.BooleanField(null=True,
                                                 blank=True,
                                                 default=None)
    open_to_job_offers = models.BooleanField(null=True,
                                             blank=True,
                                             default=None)
    expertise_areas = postgres_fields.ArrayField(enum.EnumField(ExpertiseArea),
                                                 blank=True,
                                                 default=list)
    expertise_areas_other = models.TextField(blank=True)
    available_as_speaker = models.BooleanField(null=True,
                                               blank=True,
                                               default=None)
    topics_i_speak_about = models.TextField(blank=True)
    organisational_affiliations = postgres_fields.ArrayField(
        enum.EnumField(OrganisationalAffiliation), blank=True, default=list)
    summary = models.TextField(blank=True)
    giving_pledges = postgres_fields.ArrayField(enum.EnumField(GivingPledge),
                                                blank=True,
                                                default=list)
    local_groups = models.ManyToManyField(LocalGroup,
                                          through="Membership",
                                          blank=True)
    legacy_record = models.PositiveIntegerField(null=True,
                                                default=None,
                                                editable=False,
                                                unique=True)

    slugs = contenttypes_fields.GenericRelation(ProfileSlug)

    objects = ProfileManager()

    class Meta:
        ordering = ["name", "slug"]

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return urls.reverse("profile", args=[self.slug])

    def geocode(self):
        self.lat = None
        self.lon = None
        if self.city_or_town and self.country:
            location = geocoders.Nominatim(
                timeout=10).geocode(f"{self.city_or_town}, {self.country}")
            if location:
                self.lat = location.latitude
                self.lon = location.longitude
        return self

    def get_pretty_cause_areas(self):
        return prettify_property_list(CauseArea, self.cause_areas,
                                      self.cause_areas_other)

    def get_pretty_expertise(self):
        return prettify_property_list(ExpertiseArea, self.expertise_areas,
                                      self.expertise_areas_other)

    def get_pretty_giving_pledges(self):
        if self.giving_pledges:
            return ", ".join(map(GivingPledge.label, self.giving_pledges))
        else:
            return "N/A"

    def get_pretty_organisational_affiliations(self):
        if self.organisational_affiliations:
            return ", ".join(
                map(OrganisationalAffiliation.label,
                    self.organisational_affiliations))
        else:
            return "N/A"

    def get_pretty_local_groups(self):
        if self.local_groups:
            return ", ".join([
                "{local_group}".format(local_group=x.name)
                for x in self.local_groups.all()
            ])
        else:
            return "N/A"

    def write_data_export_zip(self, request, response):
        with zipfile.ZipFile(response, mode="w") as zip_file:
            with zip_file.open(f"{self.slug}.json",
                               mode="w") as json_binary_file, io.TextIOWrapper(
                                   json_binary_file) as json_file:
                json.dump(
                    {
                        "email":
                        self.user.email,
                        "date_joined":
                        self.user.date_joined.isoformat(),
                        "last_login":
                        self.user.last_login.isoformat(),
                        "url":
                        request.build_absolute_uri(self.get_absolute_url()),
                        "is_public":
                        self.is_public,
                        "name":
                        self.name,
                        "city_or_town":
                        self.city_or_town,
                        "country":
                        self.country,
                        "cause_areas":
                        list(map(CauseArea.label, self.cause_areas)),
                        "cause_areas_other":
                        self.cause_areas_other,
                        "available_to_volunteer":
                        self.available_to_volunteer,
                        "open_to_job_offers":
                        self.open_to_job_offers,
                        "expertise_areas":
                        list(map(ExpertiseArea.label, self.expertise_areas)),
                        "expertise_areas_other":
                        self.expertise_areas_other,
                        "available_as_speaker":
                        self.available_as_speaker,
                        "topics_i_speak_about":
                        self.topics_i_speak_about,
                        "organisational_affiliations":
                        list(
                            map(
                                OrganisationalAffiliation.label,
                                self.organisational_affiliations,
                            )),
                        "summary":
                        self.summary,
                        "giving_pledges":
                        list(map(GivingPledge.label, self.giving_pledges)),
                        "member_of_local_groups": [
                            request.build_absolute_uri(
                                local_group.get_absolute_uri())
                            for local_group in self.local_groups.all()
                        ],
                        "organiser_of_local_groups": [
                            request.build_absolute_uri(
                                local_group.get_absolute_uri())
                            for local_group in self.user.localgroup_set.all()
                        ],
                        "aliases": [
                            request.build_absolute_uri(
                                urls.reverse("profile",
                                             kwargs={"slug": slug.slug}))
                            for slug in self.slugs.filter(redirect=True)
                        ],
                        "legacy_hub_url":
                        (self.legacy_record and request.build_absolute_uri(
                            urls.reverse(
                                "profile_legacy",
                                kwargs={"legacy_record": self.legacy_record},
                            ))),
                    },
                    json_file,
                    indent=2,
                )
            if self.image:
                with self.image.open() as image_src_file, zip_file.open(
                        self.slug + pathlib.PurePath(self.image.name).suffix,
                        mode="w") as image_dst_file:
                    shutil.copyfileobj(image_src_file, image_dst_file)

    def image_placeholder(self):
        return f"Avatar{self.id % 10}.png"

    def has_cause_area_details(self):
        cause_area_details_exist = [
            len(self.cause_areas) > 0,
            len(self.cause_areas_other) > 0,
            len(self.giving_pledges) > 0,
            self.available_to_volunteer,
        ]
        return any(cause_area_details_exist)

    def has_career_details(self):
        career_details_exist = [
            len(self.expertise_areas),
            len(self.expertise_areas_other),
            self.open_to_job_offers,
        ]
        return any(career_details_exist)

    def has_community_details(self):
        community_details_exist = [
            len(self.organisational_affiliations) > 0,
            self.local_groups.exists(),
            self.user.localgroup_set.exists(),
            self.available_as_speaker,
            len(self.topics_i_speak_about) > 0,
        ]
        return any(community_details_exist)
Example #25
0
class PrintIssue(models.Model, EditURLMixin):
    """ PDF file of a printed newspaper. """
    class Meta:
        # ordering = ['-publication_date']
        verbose_name = _('Pdf issue')
        verbose_name_plural = _('Pdf issues')

    issue = models.ForeignKey(Issue,
                              related_name='pdfs',
                              on_delete=models.PROTECT)

    pages = models.IntegerField(
        help_text='Number of pages',
        editable=False,
    )

    pdf = models.FileField(
        help_text=_('Pdf file for this issue.'),
        upload_to=upload_pdf_to,
    )

    cover_page = thumbnail.ImageField(
        help_text=_('An image file of the front page'),
        upload_to='pdf/covers/',
        blank=True,
        null=True,
    )

    text = models.TextField(
        help_text=_('Extracted from file.'),
        editable=False,
    )

    def __str__(self):
        if self.pdf:
            return self.pdf.url
        else:
            return super().__str__()

    def extract(self):
        return self.text[:200]

    def save(self, *args, **kwargs):
        if self.pk:
            old_self = type(self).objects.get(pk=self.pk)
        else:
            old_self = PrintIssue()
        if self.pdf and old_self.pdf != self.pdf:
            self.pages = pdf_number_of_pages(self.pdf)
            self.text = self.get_page_text_content(1, 10)
            self.cover_page.delete()
        if not self.pdf and self.cover_page:
            self.cover_page.delete()
        if self.pdf and not self.issue_id:
            publication_date = self.get_publication_date()
            self.issue, created = Issue.objects.get_or_create(
                publication_date=publication_date, )

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

    def get_cover_page(self, size=800):
        """ Get or create a jpg version of the pdf frontpage """
        if self.pdf and not self.cover_page:
            filename = Path(self.pdf.name).with_suffix('.jpg').name
            try:
                cover_image = pdf_to_image(self.pdf.file, page=1, size=size)
            except Exception as e:
                logger.exception('Failed to create cover')
                msg = 'ERROR:\n{}\nnot found on disk'.format(self.pdf.name)
                cover_image = error_image(msg, (size * 70 / 100), size)
                filename = filename.replace('.jpg', '_not_found.jpg')

            blob = BytesIO()
            cover_image.save(blob)
            self.cover_page.save(filename,
                                 ContentFile(blob.getvalue()),
                                 save=True)
        return self.cover_page

    def thumbnail(self, size='x150', **options):
        """Create thumb of frontpage"""
        try:
            image = self.get_cover_page()
            return thumbnail.get_thumbnail(image, size, **options)
        except Exception:
            logger.exception('Cannot create thumbnail')
            return BrokenImage()

    def get_page_text_content(self, first_page=1, last_page=None):
        """Extract the textual page content of a page in the pdf"""
        if last_page is None:
            last_page = first_page
        if self.pages:
            last_page = min(self.pages, last_page)
        return pdf_to_text(self.pdf, first_page, last_page)

    def get_publication_date(self):
        dateline_regex = (r'^\d(?P<day>\w+) (?P<date>\d{1,2})\.'
                          r' (?P<month>\w+) (?P<year>\d{4})')
        MONTHS = [
            'januar',
            'februar',
            'mars',
            'april',
            'mai',
            'juni',
            'juli',
            'august',
            'september',
            'oktober',
            'november',
            'desember',
        ]
        page_2_text = self.get_page_text_content(2)
        dateline = re.match(dateline_regex, page_2_text)
        if dateline:
            day = int(dateline.group('date'))
            year = int(dateline.group('year'))
            month = MONTHS.index(dateline.group('month')) + 1
            created = datetime.date(day=day, month=month, year=year)
        else:
            # Finds creation date.
            try:
                created = datetime.date.fromtimestamp(
                    os.path.getmtime(self.pdf.path))
            except NotImplementedError:
                key = self.pdf.file.key
                created = botocore.utils.parse_timestamp(key.last_modified)
            # Sets creation date as a Wednesday, if needed.
            created = created + datetime.timedelta(days=3 -
                                                   created.isoweekday())
        return created

    def get_absolute_url(self):
        return self.pdf.url

    def download_from_aws(self, dest=Path('/var/media/')):
        path = dest / self.pdf.name
        data = self.pdf.file.read()
        path.parent.mkdir(0o775, True, True)
        path.write_bytes(data)