Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import logging
2import uuid
3from datetime import timedelta
5from actstream.models import followers, following, user_stream
6from django.apps import apps
7from django.contrib.auth.models import AbstractUser
8from django.contrib.contenttypes.models import ContentType
9from django.db import models
10from django.urls import reverse
11from django.utils import timezone
12from django.utils.translation import gettext as _
13from django_bleach.models import BleachField
14from django.template.loader import render_to_string
15from guardian.shortcuts import get_objects_for_user
16from PIL import Image
17from taggit.managers import TaggableManager
18from actstream.models import Action
20from discuss_data.core.models import AffiliationTags, EnglishTags, KeywordTags, Topic
21from discuss_data.core.utils import weekly_td, daily_td
22from discuss_data.ddcomments.models import Notification
23from discuss_data.utils.cropped_thumbnail import cropped_thumbnail
25logger = logging.getLogger(__name__)
28ACADEMIC_TITLE_CHOICES = (
29 ("PHD", "PhD"),
30 ("DR", "Dr."),
31 ("PROF", "Prof. Dr."),
32 ("MA", "MA",),
33 ("BA", "BA",),
34 ("MAG", "Magister"),
35 ("M.A.", "M.A."),
36)
38ACADEMIC_TITLE_CHOICES_ORDER = {
39 "PHD": "b",
40 "DR": "f",
41 "PROF": "f",
42 "MA": "b",
43 "BA": "b",
44 "MAG": "f",
45 "M.A.": "b",
46}
49class User(AbstractUser):
51 """User profile accessibility
52 PUB - accessible for all website visitors
53 NET - accessible for logged-in users
54 HID - accessible for owner only
55 """
57 PUBLIC = "PUB"
58 NETWORK = "NET"
59 HIDDEN = "HID"
61 ACCESS_RULES_CHOICES = (
62 (PUBLIC, _("public")),
63 (NETWORK, _("internal")),
64 (HIDDEN, _("hidden")),
65 )
66 id = models.AutoField(primary_key=True)
67 uuid = models.UUIDField(
68 default=uuid.uuid4, editable=False
69 ) # uuid _not_ as pk as this disturbs django 3rd party apps
70 middle_name = models.CharField(max_length=200, blank=True)
71 academic_title = models.CharField(
72 max_length=5, choices=ACADEMIC_TITLE_CHOICES, blank="None"
73 ) # deprecated
74 name_prefix = models.CharField(
75 max_length=200, blank=True, verbose_name="prefix", help_text='for example "Dr"',
76 )
77 name_suffix = models.CharField(
78 max_length=200,
79 blank=True,
80 verbose_name="suffix",
81 help_text='for example "PhD"',
82 )
83 # position = models.CharField(max_length=600)
84 # institution = models.ForeignKey('Institution', blank=True, null=True, related_name='user_profile_institution')
85 # institutions = models.ManyToManyField('Institution', blank=True,)
86 photo = models.ImageField(blank=True, null=True)
87 # fields_of_interest = models.ManyToManyField('FOI', blank=True)
88 topics = models.ManyToManyField(Topic, blank=True)
89 interests = TaggableManager(blank=True, through=KeywordTags)
90 receptive_tos = models.ManyToManyField("ReceptiveTo", blank=True)
91 countries = models.ManyToManyField(
92 "Country", related_name="user_country", blank=True
93 )
94 research_profile_full = BleachField(
95 blank=True, max_length=12000, verbose_name="Research Profile (full)"
96 )
97 research_profile_short = BleachField(
98 max_length=280,
99 blank=True,
100 verbose_name="Research Profile (brief)",
101 help_text="brief version, max. 280 characters",
102 )
103 profile_accessibility = models.CharField(
104 max_length=3, choices=ACCESS_RULES_CHOICES, default=NETWORK
105 )
106 publications = models.ManyToManyField(
107 "ddpublications.Publication", blank=True, related_name="user_publication",
108 )
109 en_tags = TaggableManager(blank=True, through=EnglishTags)
110 affiliation_tags = TaggableManager(
111 "affiliations", blank=True, through=AffiliationTags
112 )
113 external_profile = models.URLField(blank=True)
115 accepted_dariah_tou = models.BooleanField(
116 verbose_name="Accept DARIAH TOU", default=False
117 )
119 accepted_dd_tou = models.BooleanField(
120 verbose_name="Accept Discuss Data TOU", default=False
121 )
123 show_first_steps = models.BooleanField(
124 verbose_name="Show First Steps", default=True
125 )
126 email_alternative = models.EmailField(blank=True)
128 class Meta:
129 ordering = ["last_name"]
131 def get_academic_name(self):
132 a_n = list()
133 if self.name_prefix:
134 a_n.append(self.name_prefix)
136 if self.first_name:
137 a_n.append(self.first_name)
139 if self.middle_name:
140 a_n.append(self.middle_name)
142 if self.last_name:
143 a_n.append(self.last_name)
145 academic_name = " ".join(a_n)
147 if self.name_suffix:
148 academic_name = academic_name + ", " + self.name_suffix
150 return academic_name
152 def get_email(self):
153 if self.email_alternative:
154 return self.email_alternative
155 else:
156 return self.email
158 def get_publications(self):
159 return self.publications.all().order_by("-year")
161 def get_countries(self):
162 return self.countries.all().order_by("name")
164 def get_interests(self):
165 return self.interests.all().order_by("name")
167 def get_main_affiliation(self):
168 return Affiliation.objects.filter(user=self).filter(main=True)
170 def get_affiliations(self):
171 return self.affiliation_user.all().order_by("list_position")
173 def get_lists_public(self):
174 model = apps.get_model("dddatasets", "datalist")
175 return model.objects.filter(public=True, owner=self)
177 def get_lists_all(self):
178 model = apps.get_model("dddatasets", "datalist")
179 return model.objects.filter(owner=self)
181 def get_published_admin_datasets(self):
182 model = apps.get_model("dddatasets", "dataset")
183 return get_objects_for_user(self, "admin_dsmo", model).filter(published=True)
185 def get_published_datasets(self):
186 # use get model to avoid circular import
187 model = apps.get_model("dddatasets", "datasetmanagementobject")
188 dsmos = model.objects.filter(owner=self).filter(published=True)
189 datasets = list()
190 for dsmo in dsmos:
191 if dsmo.get_top_version_published_dataset():
192 datasets.append(dsmo.get_top_version_published_dataset())
193 return datasets
195 def get_published_datasets_count(self):
196 return len(self.get_published_datasets())
198 def curated_categories(self):
199 return self.category_curators_user.all()
201 def get_curated_datasets(self):
202 model = apps.get_model("dddatasets", "dataset")
203 datasets = model.objects.filter(
204 dataset_management_object__main_category__in=self.curated_categories()
205 )
206 return datasets
208 def get_all_datasets(self):
209 return self.dataset_set.all()
211 def get_projects(self):
212 return Project.objects.filter(user=self).order_by("name")
213 # projects_list = list()
214 # for project in Project.objects.filter(user=self):
215 # projects_list.append(project)
216 # return projects_list
218 def __str__(self):
219 return self.get_academic_name()
221 def get_notifications_curators(self, dataset_content_type):
222 user_curated_datasets = list(
223 self.get_curated_datasets().values_list("id", flat=True)
224 )
225 notifications_curators = Notification.objects.filter(
226 content_type=dataset_content_type.id, object_id__in=user_curated_datasets
227 )
228 return notifications_curators
230 def get_notifications(self):
231 user_all_datasets = list(self.get_all_datasets().values_list("id", flat=True))
233 user_admin_datasets = list(
234 self.get_published_admin_datasets().values_list("id", flat=True)
235 )
237 user_notifications_to = Notification.objects.filter(recipient=self)
239 dataset_content_type = ContentType.objects.get(
240 app_label="dddatasets", model="dataset"
241 )
243 datasets_notifications = Notification.objects.filter(
244 content_type=dataset_content_type.id, object_id__in=user_all_datasets
245 )
247 datasets_notifications_admins = Notification.objects.filter(
248 content_type=dataset_content_type.id, object_id__in=user_admin_datasets
249 )
251 datasets_notifications_curators = self.get_notifications_curators(
252 dataset_content_type
253 )
255 notifications = (
256 user_notifications_to
257 | datasets_notifications
258 | datasets_notifications_curators
259 | datasets_notifications_admins
260 )
261 return notifications
263 def get_notifications_count(self):
264 return self.get_notifications().count()
266 def get_notifications_recent(self):
267 startdate = timezone.now()
268 enddate = startdate - timedelta(days=7)
269 return (
270 self.get_notifications()
271 .filter(date_added__lte=startdate)
272 .filter(date_added__gte=enddate)
273 )
275 def get_notifications_recent_count(self):
276 return self.get_notifications_recent().count()
278 def get_following_actions(self):
279 return user_stream(self)
281 def get_following_actions_recent_weekly(self):
282 startdate, enddate = weekly_td()
283 return (
284 self.get_following_actions()
285 .filter(timestamp__lte=startdate)
286 .filter(timestamp__gte=enddate)
287 )
289 def get_following_actions_recent_daily(self):
290 startdate, enddate = daily_td()
291 return (
292 self.get_following_actions()
293 .filter(timestamp__lte=startdate)
294 .filter(timestamp__gte=enddate)
295 )
297 def get_datasets_actions(self):
298 user_admin_datasets = list(
299 self.get_published_admin_datasets().values_list("id", flat=True)
300 )
301 actor_ctype = ContentType.objects.get(app_label="ddusers", model="user")
302 return Action.objects.datasets_actions(user_admin_datasets).exclude(
303 actor_content_type=actor_ctype, actor_object_id=self.id
304 )
306 def get_datasets_actions_recent_weekly(self):
307 startdate, enddate = weekly_td()
308 return (
309 self.get_datasets_actions()
310 .filter(timestamp__lte=startdate)
311 .filter(timestamp__gte=enddate)
312 )
314 def get_datasets_actions_recent_daily(self):
315 startdate, enddate = daily_td()
316 return (
317 self.get_datasets_actions()
318 .filter(timestamp__lte=startdate)
319 .filter(timestamp__gte=enddate)
320 )
322 def get_user_actions(self):
323 return Action.objects.any_everything(self)
325 def get_user_actions_recent_weekly(self):
326 startdate, enddate = weekly_td()
327 return (
328 self.get_user_actions()
329 .filter(timestamp__lte=startdate)
330 .filter(timestamp__gte=enddate)
331 )
333 def get_user_actions_recent_daily(self):
334 startdate, enddate = daily_td()
335 return (
336 self.get_user_actions()
337 .filter(timestamp__lte=startdate)
338 .filter(timestamp__gte=enddate)
339 )
341 def compose_status_email_daily(self):
342 # daily status email
343 feed_actions = self.get_user_actions_recent_daily()
344 return render_to_string(
345 "ddusers/status_email.html", {"user": self, "feed_actions": feed_actions}
346 )
348 def compose_status_email_weekly(self):
349 # weekly status email
350 feed_actions = self.get_user_actions_recent_weekly()
351 return render_to_string(
352 "ddusers/status_email.html", {"user": self, "feed_actions": feed_actions}
353 )
355 def get_following(self):
356 return following(self, User)
358 def get_followers(self):
359 return followers(self)
361 def get_following_count(self):
362 return len(following(self, User))
364 def get_followers_count(self):
365 return len(followers(self))
367 def group_member(self, group):
368 return self.groups.filter(id=group.id).exists()
370 @property
371 def image_url(self):
372 try:
373 return self.photo.path
374 except Exception:
375 return "/static/images/user_default.png"
377 def get_activities(self):
378 return user_stream(self, with_user_activity=True)
380 def get_absolute_url(self):
381 return reverse("ddusers:detail", args=[str(self.uuid)])
383 def class_name(self):
384 return self.__class__.__name__
386 # override for thumbnail generation
387 def save(self, *args, **kwargs):
388 super().save(*args, **kwargs)
389 if self.photo and hasattr(self.photo, "url"):
390 # in situ thumbnail generation at upload
391 im = Image.open(self.photo.path)
392 thumbnail = cropped_thumbnail(im, [250, 250])
393 thumbnail.save(self.photo.path)
395 if kwargs.get("force_insert"):
396 kwargs.pop("force_insert")
397 super().save(*args, **kwargs)
400class ReceptiveTo(models.Model):
401 text = models.CharField(max_length=200, blank=True)
403 def __str__(self):
404 return self.text
407class City(models.Model):
408 name = models.CharField(max_length=400)
409 slug = models.SlugField()
410 geo_lat = models.CharField(max_length=200, blank=True)
411 geo_lng = models.CharField(max_length=200, blank=True)
412 country = models.ForeignKey(
413 "Country", related_name="city_country", on_delete=models.CASCADE
414 )
415 public = models.BooleanField(default=True)
417 def __str__(self):
418 return self.name
421class Country(models.Model):
422 name = models.CharField(max_length=200)
423 slug = models.SlugField()
424 geo_lat = models.CharField(max_length=200)
425 geo_lng = models.CharField(max_length=200)
426 public = models.BooleanField(default=True)
428 class Meta:
429 ordering = ["name"]
431 def __str__(self):
432 return self.name
435class FOI(models.Model):
436 name = models.CharField(max_length=200)
437 description = BleachField(max_length=1200, blank=True)
438 is_country = models.BooleanField(default=False)
440 def __str__(self):
441 return self.name
444class Affiliation(models.Model):
445 uuid = models.UUIDField(
446 default=uuid.uuid4, editable=False
447 ) # uuid _not_ as pk as this disturbs django 3rd party apps
448 user = models.ForeignKey(
449 "User", related_name="affiliation_user", on_delete=models.CASCADE
450 )
451 institution = models.ForeignKey(
452 "Institution",
453 related_name="affiliation_institution",
454 blank=True,
455 null=True,
456 on_delete=models.CASCADE,
457 )
458 name_of_institution = models.CharField(max_length=200)
459 place_of_institution = models.CharField(max_length=200)
460 country_of_institution = models.CharField(max_length=200)
461 position = models.CharField(max_length=200)
462 website_of_institution = models.URLField(blank=True)
463 main = models.BooleanField(default=True)
464 list_position = models.IntegerField(null=True, blank=True)
466 def get_institution(self):
467 return "{}, {}, {}".format(
468 self.name_of_institution,
469 self.place_of_institution,
470 self.country_of_institution,
471 )
473 def __str__(self):
474 return "%s, %s, %s, %s" % (
475 self.position,
476 self.name_of_institution,
477 self.place_of_institution,
478 self.country_of_institution,
479 )
482class Institution(models.Model):
483 uuid = models.UUIDField(
484 default=uuid.uuid4, editable=False
485 ) # uuid _not_ as pk as this disturbs django 3rd party apps
486 name = models.CharField(max_length=600)
487 original_name = models.CharField(max_length=600, blank=True)
488 short_name = models.CharField(max_length=60, blank=True)
489 department = models.CharField(max_length=600, blank=True)
490 address = models.CharField(max_length=600)
491 postcode = models.CharField(max_length=40, blank=True, null=True)
492 image = models.ImageField(blank=True, null=True)
493 image_large = models.ImageField(blank=True, null=True)
494 website = models.URLField(blank=True)
495 description = BleachField(max_length=1200, blank=True)
496 short_description = models.CharField(max_length=400, blank=True)
497 location = models.CharField(max_length=20, blank=True)
498 city = models.CharField(max_length=120)
499 country = models.ForeignKey(
500 "Country",
501 related_name="institution_country",
502 blank=True,
503 on_delete=models.CASCADE,
504 )
505 public = models.BooleanField(default=True)
506 admin = models.ForeignKey(
507 "User",
508 related_name="institution_admin_user",
509 blank=True,
510 null=True,
511 on_delete=models.CASCADE,
512 )
513 affiliated_users = models.ManyToManyField("User", blank=True,)
515 def __str__(self):
516 return self.name
519class Project(models.Model):
520 uuid = models.UUIDField(
521 default=uuid.uuid4, editable=False
522 ) # uuid _not_ as pk as this disturbs django 3rd party apps
523 user = models.ForeignKey(
524 "User", related_name="project_user", on_delete=models.CASCADE
525 )
526 # group = models.ForeignKey(
527 # Group,
528 # blank=True,
529 # null=True,
530 # related_name="project_group",
531 # on_delete=models.CASCADE,
532 # )
533 name = models.CharField(max_length=200)
534 url = models.URLField(blank=True)
535 description = BleachField(blank=True, max_length=2400)
537 def __str__(self):
538 return self.name