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
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
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/'
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']
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
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"
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})
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)
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
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
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)
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,))
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
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) })
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)
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 ''
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)
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))
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 ]
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
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
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)
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()
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)
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)