class Migration(migrations.Migration): dependencies = [ ("contenttypes", "0002_remove_content_type_name"), ("profiles", "0007_profile_is_public"), ] operations = [ migrations.AlterField( model_name="profile", name="slug", field=sluggable_fields.SluggableField(unique=True), ), migrations.CreateModel( name="ProfileSlug", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("object_id", models.PositiveIntegerField()), ( "slug", models.CharField(db_index=True, max_length=255, unique=True, verbose_name="URL"), ), ( "redirect", models.BooleanField(default=False, verbose_name="Redirection"), ), ("created", models.DateTimeField(auto_now_add=True)), ( "content_type", models.ForeignKey(on_delete=deletion.PROTECT, to="contenttypes.ContentType"), ), ], options={"abstract": False}, ), migrations.RunPython(code=create_slugs, reverse_code=migrations.RunPython.noop), ]
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 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)