class Country(models.Model): code = models.CharField(max_length=2, unique=True) alpha_3_code = models.CharField(max_length=3, blank=True, default="") name = models.CharField(max_length=75) region = models.ForeignKey(Region, null=True, blank=True, related_name="countries") population = models.IntegerField(null=True, blank=True) primary_networks = models.ManyToManyField( Network, blank=True, db_table='uw_country_primary_networks') extra_data = JSONField(blank=True) tracker = FieldTracker() class Meta: db_table = 'uw_country' def gateway_language(self): if not hasattr(self, "_gateway_language"): data = self.extra_data if not isinstance(data, dict): data = {} self._gateway_language = next( iter(Language.objects.filter( code=data.get("gateway_language"))), None) return self._gateway_language def gateway_languages(self, with_primary=True): gl = self.gateway_language() if gl: ogls = [gl] else: ogls = [] for lang in self.language_set.all(): if lang.gateway_flag and lang not in ogls: ogls.append(lang) elif lang.gateway_language and lang.gateway_language not in ogls: ogls.append(lang.gateway_language) if not with_primary and gl: ogls.remove(gl) return ogls @classmethod def regions(cls): qs = cls.objects.all().values_list("region", flat=True).distinct() qs = qs.order_by("region.name") return qs @classmethod def gateway_data(cls): with_gateways = cls.objects.filter( language__gateway_language__isnull=False).distinct() without_gateways = cls.objects.exclude(pk__in=with_gateways) data = { x.code: { "obj": x, "gateways": defaultdict(lambda: []) } for x in with_gateways } data.update({ x.code: { "obj": x, "gateways": { "n/a": list(x.language_set.all()) } } for x in without_gateways }) for country in with_gateways: for lang in country.language_set.all(): if lang.gateway_language: data[country.code]["gateways"][ lang.gateway_language.code].append(lang) else: data[country.code]["gateways"]["n/a"].append(lang) return data def __str__(self): return self.name
class Base(SoftDeletableModel, TimeStampedModel): tracker = FieldTracker() class Meta: abstract = True
class People(index.Indexed, ClusterableModel, StatusModel, TimeStampedModel): """ A Django model to store People objects. It uses the `@register_snippet` decorator to allow it to be accessible via the Snippets UI (e.g. /admin/snippets/base/people/) `People` uses the `ClusterableModel`, which allows the relationship with another model to be stored locally to the 'parent' model (e.g. a PageModel) until the parent is explicitly saved. This allows the editor to use the 'Preview' button, to preview the content, without saving the relationships to the database. https://github.com/wagtail/django-modelcluster """ first_name = models.CharField("First Name", max_length=254) last_name = models.CharField("Last Name", max_length=254) phone_number = PhoneNumberField("Phone Number") email_address = models.EmailField("Email Address") position = models.CharField("Position", max_length=254) bio = RichTextField(blank=True) facebook = models.URLField( "Facebook Link", max_length=254, blank=True, help_text= "(Optional) Enter Facebook Profile Link, for example: https://www.facebook.com/conrad.mbewe.1", ) twitter = models.URLField( "Twitter Link", max_length=254, blank=True, help_text= "(Optional) Enter Twitter Profile Link, for example: https://twitter.com/voddiebaucham", ) linked_in = models.URLField( "LinkedIn Link", max_length=254, blank=True, help_text= "(Optional) Enter LinkedIn Profile Link, for example: https://www.linkedin.com/in/justin-lupele-phd-31a35092", ) STATUS = Choices("draft", "published") # https://django-model-utils.readthedocs.io/en/latest/utilities.html#field-tracker tracker = FieldTracker() image = models.ForeignKey( "wagtailimages.Image", null=True, # blank=True, # required on_delete=models.SET_NULL, related_name="+", ) panels = [ FieldPanel("status"), MultiFieldPanel( [ FieldRowPanel([ FieldPanel("first_name", classname="col6"), FieldPanel("last_name", classname="col6"), ]) ], "Name", ), MultiFieldPanel( [ FieldRowPanel([ FieldPanel("phone_number", classname="col6"), FieldPanel("email_address", classname="col6"), ]) ], "Contact Details", ), FieldPanel("position"), FieldPanel("bio", classname="full"), ImageChooserPanel("image"), MultiFieldPanel( [ FieldPanel("facebook", classname="full"), FieldPanel("twitter", classname="full"), FieldPanel("linked_in", classname="full"), ], "Social Media Links", ), ] search_fields = [ index.SearchField("first_name", partial_match=True), index.SearchField("last_name", partial_match=True), ] @property def thumb_image(self): # Returns an empty string if there is no profile pic or the rendition # file can't be found. try: return self.image.get_rendition("fill-50x50").img_tag() except: # noqa: E:722 return "" def __str__(self): return "{} {}".format(self.first_name, self.last_name) class Meta: verbose_name = "Person" verbose_name_plural = "People"
class Order(models.Model): class OrderStatus(models.TextChoices): CALL = 'call', 'Call & Void' CANCELLED = 'cancelled', 'Cancelled' FRAUD = 'fraud', 'Fraud' NEW = 'new', 'New' UNVERIFIED = 'unverified', 'Unverified' VALID = 'valid', 'Valid' account = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, null=False, on_delete=models.CASCADE, related_name='order_account') billing_invoice = models.ForeignKey('BillingInvoice', blank=False, null=True, on_delete=models.CASCADE, related_name='order_billing_invoice') billing_invoice_transaction = models.ForeignKey( 'BillingInvoiceTransaction', blank=False, null=True, on_delete=models.CASCADE, related_name='order_billing_invoice_transaction') billing_profile = models.ForeignKey('BillingProfile', blank=False, null=False, on_delete=models.CASCADE, related_name='order_billing_profile') company = models.ForeignKey('Company', blank=False, null=False, on_delete=models.CASCADE, related_name='order_company') product_profile = models.ForeignKey('ProductProfile', blank=False, null=False, on_delete=models.CASCADE, related_name='order_product_profile') date_from = models.DateTimeField(auto_now_add=True) status = models.CharField(blank=False, choices=OrderStatus.choices, max_length=10, null=False) tracker = FieldTracker() class Meta: db_table = 'order' default_permissions = () verbose_name = 'Order' verbose_name_plural = 'Orders'
class ExpertRequest(core_models.UuidMixin, core_models.NameMixin, PriceMixin, common_mixins.ProductCodeMixin, structure_models.StructureLoggableMixin, structure_models.TimeStampedModel): class States(object): PENDING = 'pending' ACTIVE = 'active' CANCELLED = 'cancelled' COMPLETED = 'completed' CHOICES = ((PENDING, _('Pending')), (ACTIVE, _('Active')), (CANCELLED, _('Cancelled')), (COMPLETED, _('Completed'))) description = models.TextField(blank=True) user = models.ForeignKey( core_models.User, related_name='+', on_delete=models.CASCADE, help_text=_('The user which has created this request.')) project = models.ForeignKey(structure_models.Project, related_name='+', on_delete=models.SET_NULL, null=True) # Project name, project UUID, customer should be stored separately # because they are not available after project removal project_name = models.CharField(max_length=150, blank=True) project_uuid = models.CharField(max_length=32, blank=True) customer = models.ForeignKey(structure_models.Customer, related_name='+', on_delete=models.CASCADE, null=True) state = models.CharField(default=States.PENDING, max_length=30, choices=States.CHOICES) type = models.CharField(max_length=255) extra = core_fields.JSONField(default={}) issue = models.ForeignKey(support_models.Issue, null=True, on_delete=models.SET_NULL) recurring_billing = models.BooleanField( default=False, help_text= _('Defines whether expert request has to be billed every month or only once' )) objectives = models.TextField(blank=True) milestones = models.TextField(blank=True) contract_methodology = models.TextField(blank=True) out_of_scope = models.TextField( blank=True, help_text=_('Elements that are explicitly excluded from the contract')) common_tos = models.TextField(blank=True) tracker = FieldTracker() objects = managers.ExpertRequestManager() class Meta: ordering = ['-created'] def get_log_fields(self): return super(ExpertRequest, self).get_log_fields() + ('state', 'project', 'user') @classmethod def get_url_name(cls): return 'expert-request' @property def type_label(self): offerings = settings.WALDUR_SUPPORT.get('CONTRACT', {}).get('offerings', {}) type_settings = offerings.get(self.type, {}) return type_settings.get('label', None) @property def planned_budget(self): price = self.extra.get('price') if price: try: return float(price) except ValueError: return 0 return 0 def __str__(self): if self.project: return '%s (%s)' % (self.name, self.project) return self.name
class _concept(baseAristotleObject): """ This is the base concrete class that ``Status`` items attach to, and to which collection objects refer to. It is not marked abstract in the Django Meta class, and **must not be inherited from**. It has relatively few fields and is a convenience class to link with in relationships. """ objects = ConceptManager() template = "aristotle_mdr/concepts/managedContent.html" workgroup = models.ForeignKey(Workgroup, related_name="items", null=True, blank=True) submitter = models.ForeignKey( User, related_name="created_items", null=True, blank=True, help_text=_('This is the person who first created an item. Users can always see items they made.')) # We will query on these, so want them cached with the items themselves # To be usable these must be updated when statuses are changed _is_public = models.BooleanField(default=False) _is_locked = models.BooleanField(default=False) tracker = FieldTracker() comparator = comparators.Comparator edit_page_excludes = None admin_page_excludes = None class Meta: # So the url_name works for items we can't determine. verbose_name = "item" @property def non_cached_fields_changed(self): changed = self.tracker.changed() public_changed = changed.pop('_is_public', False) locked_changed = changed.pop('_is_locked', False) return len(changed.keys()) > 0 def can_edit(self, user): return _concept.objects.filter(pk=self.pk).editable(user).exists() def can_view(self, user): return _concept.objects.filter(pk=self.pk).visible(user).exists() @property def item(self): """ Performs a lookup using ``model_utils.managers.InheritanceManager`` to find the subclassed item. """ return _concept.objects.get_subclass(pk=self.pk) @property def concept(self): """ Returns the parent _concept that an item is built on. If the item type is _concept, return itself. """ return getattr(self, '_concept_ptr', self) @classmethod def get_autocomplete_name(self): return 'Autocomplete' + "".join( self._meta.verbose_name.title().split() ) @staticmethod def autocomplete_search_fields(self): return ("name__icontains",) def get_absolute_url(self): return url_slugify_concept(self) @property def registry_cascade_items(self): """ This returns the items that can be registered along with the this item. If a subclass of _concept defines this method, then when an instance of that class is registered using a cascading method then that instance, all instances returned by this method will all recieve the same registration status. Reimplementations of this MUST return iterables. """ return [] @property def is_registered(self): return self.statuses.count() > 0 @property def is_superseded(self): return all( STATES.superseded == status.state for status in self.statuses.all() ) and self.superseded_by @property def is_retired(self): return all( STATES.retired == status.state for status in self.statuses.all() ) and self.statuses.count() > 0 def check_is_public(self, when=timezone.now()): """ A concept is public if any registration authority has advanced it to a public state in that RA. """ statuses = self.statuses.all() statuses = self.current_statuses(qs=statuses, when=when) pub_state = True in [ s.state >= s.registrationAuthority.public_state for s in statuses ] q = Q() extra = False extra_q = settings.ARISTOTLE_SETTINGS.get('EXTRA_CONCEPT_QUERYSETS', {}).get('public', None) if extra_q: for func in extra_q: q |= import_string(func)() extra = self.__class__.objects.filter(pk=self.pk).filter(q).exists() return pub_state or extra def is_public(self): return self._is_public is_public.boolean = True is_public.short_description = 'Public' def check_is_locked(self, when=timezone.now()): """ A concept is locked if any registration authority has advanced it to a locked state in that RA. """ statuses = self.statuses.all() statuses = self.current_statuses(qs=statuses, when=when) return True in [ s.state >= s.registrationAuthority.locked_state for s in statuses ] def is_locked(self): return self._is_locked is_locked.boolean = True is_locked.short_description = 'Locked' def recache_states(self): self._is_public = self.check_is_public() self._is_locked = self.check_is_locked() self.save() concept_visibility_updated.send(sender=self.__class__, concept=self) def current_statuses(self, qs=None, when=timezone.now()): if qs is None: qs = self.statuses.all() if hasattr(when, 'date'): when = when.date() registered_before_now = Q(registrationDate__lte=when) registation_still_valid = ( Q(until_date__gte=when) | Q(until_date__isnull=True) ) states = qs.filter( registered_before_now & registation_still_valid ).order_by("registrationAuthority", "-registrationDate", "-created") from django.db import connection if connection.vendor == 'postgresql': states = states.distinct('registrationAuthority') else: current_ids = [] seen_ras = [] for s in states: ra = s.registrationAuthority if ra not in seen_ras: current_ids.append(s.pk) seen_ras.append(ra) # We hit again so we can return this as a queryset states = states.filter(pk__in=current_ids) return states def get_download_items(self): """ When downloading a concept, extra items can be included for download by overriding the ``get_download_items`` method on your item. By default this returns an empty list, but can be modified to include any number of items that inherit from ``_concept``. When overriding, each entry in the list must be a two item tuple, with the first entry being the python class of the item or items being included, and the second being the queryset of items to include. """ return []
class Person(BaseEntity): # Basic Information given_name = models.CharField('Given Name', max_length=100) family_name = models.CharField('Family Name', max_length=100) email = models.EmailField('Email Address') member_since = models.DateField('Date Joined', blank=True, null=True) household = models.ForeignKey('Household', on_delete=models.SET_NULL, blank=True, null=True) student_team = models.ForeignKey('StudentTeam', on_delete=models.SET_NULL, blank=True, null=True) # Address address_street1 = models.CharField('Street Address', max_length=100) address_street2 = models.CharField('Street Address 2', max_length=100, blank=True, null=True) address_city = models.CharField('City', max_length=100) address_state = USStateField('State') address_zip = USZipCodeField('Zip Code') # Phone Info phone_number = PhoneNumberField() phone_can_receive_sms = models.BooleanField(null=False, default=False) # Emergency Contact Information emergency_contact_name = models.CharField(max_length=100, blank=True, null=True) emergency_contact_phone = PhoneNumberField(blank=True, null=True) # Temporary, until CiviCRM import is complete civicrm_contact_id = models.CharField('CiviCRM Contact ID', max_length=5, null=True, blank=True) # Managers objects = managers.MemberManager() # This tells us which fields have been modified so custom save methods # and signal handlers can do less work. tracker = FieldTracker() def __str__(self): return ", ".join([self.family_name, self.given_name]) def address_lines(self): ret = [self.address_street1] if self.address_street2: ret.append(self.address_street2) ret.append("%s %s, %s" % (self.address_city, self.address_state, self.address_zip)) return ret def membership_status(self): if self.household: return self.household.status if self.student_team: return self.student_team.status return 'none' def household_or_student_team(self): if self.household: return self.household.name if self.student_team: return self.student_team.name return None #### custom validation of member fields #### def __household_has_vacancies(self): if self.household == None: return True if self.household.has_vacancy_for(self): return True return False def clean(self): if not self.__household_has_vacancies(): raise ValidationError( {'household': 'The household is already full.'}) if self.household and self.student_team: raise ValidationError({ 'household': 'One cannot join both a household and a student team', 'student_team': 'One cannot join both a household and a student team', }) super().clean() def save(self, *args, **kwargs): if not self.tracker.has_changed('household_id'): return super().save(*args, **kwargs) if not self.__household_has_vacancies(): raise DataError('Household is already full') if self.household_id and self.student_team_id: raise DataError( 'One cannot join both a household and a student team') # we're in the clear super().save(*args, **kwargs)
class Issue(core_models.UuidMixin, structure_models.StructureLoggableMixin, core_models.BackendModelMixin, TimeStampedModel, core_models.StateMixin): class Meta: ordering = ['-created'] class Permissions(object): customer_path = 'customer' project_path = 'project' backend_id = models.CharField(max_length=255, blank=True, null=True, unique=True) key = models.CharField(max_length=255, blank=True) type = models.CharField(max_length=255) link = models.URLField(max_length=255, help_text=_('Link to issue in support system.'), blank=True) summary = models.CharField(max_length=255) description = models.TextField(blank=True) deadline = models.DateTimeField(blank=True, null=True) impact = models.CharField(max_length=255, blank=True) status = models.CharField(max_length=255) resolution = models.CharField(max_length=255, blank=True) priority = models.CharField(max_length=255, blank=True) caller = models.ForeignKey( settings.AUTH_USER_MODEL, related_name='created_issues', help_text=_('Waldur user who has reported the issue.'), on_delete=models.PROTECT) reporter = models.ForeignKey( 'SupportUser', related_name='reported_issues', blank=True, null=True, help_text= _('Help desk user who have created the issue that is reported by caller.' ), on_delete=models.PROTECT) assignee = models.ForeignKey( 'SupportUser', related_name='issues', blank=True, null=True, help_text=_('Help desk user who will implement the issue'), on_delete=models.PROTECT) customer = models.ForeignKey(structure_models.Customer, verbose_name=_('organization'), related_name='issues', blank=True, null=True, on_delete=models.CASCADE) project = models.ForeignKey(structure_models.Project, related_name='issues', blank=True, null=True, on_delete=models.CASCADE) resource_content_type = models.ForeignKey(ContentType, null=True) resource_object_id = models.PositiveIntegerField(null=True) resource = GenericForeignKey('resource_content_type', 'resource_object_id') first_response_sla = models.DateTimeField(blank=True, null=True) tracker = FieldTracker() def get_description(self): return self.description @classmethod def get_url_name(cls): return 'support-issue' @classmethod def get_backend_fields(cls): return super(Issue, cls).get_backend_fields() + ( 'backend_id', 'key', 'type', 'link', 'summary', 'description', 'deadline', 'impact', 'status', 'resolution', 'priority', 'caller', 'reporter', 'assignee', 'customer', 'project', 'resource', 'first_response_sla') def get_log_fields(self): return ('uuid', 'type', 'key', 'status', 'link', 'summary', 'reporter', 'caller', 'customer', 'project', 'resource') def __str__(self): return '{}: {}'.format(self.key or '???', self.summary)
class Thermostat(NameBaseModel): """ Store thermostat data. """ house = models.ForeignKey(House, related_name='thermostats', on_delete=models.CASCADE, help_text='Related house.') mode = models.CharField(choices=MODES, default=MODES.off, max_length=5, help_text='Current mode of the thermostat.') current_temperature = models.DecimalField( decimal_places=2, max_digits=5, help_text='Current temperature at the thermostat.') temperature_set_point = models.DecimalField( decimal_places=2, max_digits=5, help_text='Temperature set point.') tracker = FieldTracker() track_records = GenericRelation( TrackRecord, object_id_field='target_object_id', content_type_field='target_content_type', related_query_name="Thermostat track records") def __str__(self): return self.name def save(self, *args, **kwargs): if self.pk is None: return super(Thermostat, self).save(*args, **kwargs) # If its an update to the record and mode or temperature is changed, # Keep a track record for each change in temperature or mode if self.tracker.has_changed("current_temperature"): TrackRecord.objects.create( name=self.name, state_type="Temperature", target_content_type=ContentType.objects.get_for_model( Thermostat), target_object_id=self.pk, from_state=self.tracker.previous("current_temperature"), to_state=self.current_temperature, ) if self.tracker.has_changed("temperature_set_point"): TrackRecord.objects.create( name=self.name, state_type="Temperature set point", target_content_type=ContentType.objects.get_for_model( Thermostat), target_object_id=self.pk, from_state=self.tracker.previous("temperature_set_point"), to_state=self.temperature_set_point, ) if self.tracker.has_changed("mode"): TrackRecord.objects.create( name=self.name, state_type="Mode", target_content_type=ContentType.objects.get_for_model( Thermostat), target_object_id=self.pk, from_state=self.tracker.previous("mode"), to_state=self.mode, ) return super(Thermostat, self).save(*args, **kwargs)
class Action(models.Model): STATUSES_WITHOUT_ASSIGNED_VOLUNTEER = ( ActionStatus.INTEREST, ActionStatus.PENDING) external_action_id = models.CharField( max_length=50, null=True, blank=True, help_text="The ID of the action in an external system") added_by = models.ForeignKey(user_models.Coordinator, related_name='added_by', on_delete=models.PROTECT, help_text="What's your name?") coordinator = models.ForeignKey(user_models.Coordinator, related_name='coordinator', on_delete=models.PROTECT, help_text="Who will mediate this action?") call_datetime = models.DateTimeField( null=True, help_text="What time did you receive the call about this action?") call_duration = models.DurationField( null=True, blank=True, help_text="How long was the call?") resident = models.ForeignKey( user_models.Resident, on_delete=models.PROTECT, null=True, help_text="Who made the request?") requested_datetime = models.DateTimeField( null=True, verbose_name="Due", help_text="When should the action be completed by?") interested_volunteers = models.ManyToManyField(user_models.Volunteer, blank=True, related_name="actions_interested_in", help_text="Volunteers who have expressed interest in completing the action..") assigned_volunteer = models.ForeignKey(user_models.Volunteer, on_delete=models.PROTECT, null=True, blank=True, help_text="The volunteer who will complete the action.") action_status = models.CharField(max_length=1, choices=ActionStatus.STATUSES, default=ActionStatus.PENDING, help_text="What's the status of this action?") action_priority = models.CharField(max_length=1, choices=ActionPriority.PRIORITIES, default=ActionPriority.MEDIUM, help_text="What priority should this action be given?") public_description = models.TextField(max_length=500, null=True, blank=True, help_text="Text that gets displayed to volunteers who are browsing actions.") private_description = models.TextField( null=True, blank=True, help_text="Text that only gets displayed to a volunteer when they're assigned to the action.") help_type = models.ForeignKey(HelpType, on_delete=models.PROTECT, null=True, verbose_name="Action type", help_text="Which kind of help is needed") requirements = models.ManyToManyField(Requirement, blank=True, related_name="actions", help_text="Only volunteers matching these requirements will see the action.") volunteer_made_contact_on = models.DateTimeField(null=True, blank=True) assigned_date = models.DateTimeField( null=True, blank=True, verbose_name="Assigned on") completed_date = models.DateTimeField( null=True, blank=True, verbose_name="Completed on") action_uuid = models.UUIDField(default=uuid.uuid4, unique=True, db_index=True) time_taken = models.DurationField(null=True, blank=True) # Track changes to the model so we can access the previous status # when it changes, and update the volunteer accordingly if it swapped # to a status that doesn't have a volunteer assigned tracker = FieldTracker() def save(self, force_insert=False, force_update=False, using=None, update_fields=None): # Ensure that the volunteer gets cleared when we move from a status # that has an assigned volunteer to one that doesn't. if (self.tracker.has_changed('action_status') and self.action_status in self.STATUSES_WITHOUT_ASSIGNED_VOLUNTEER and self.tracker.previous('action_status') not in self.STATUSES_WITHOUT_ASSIGNED_VOLUNTEER and self.tracker.previous('action_status') is not None): self.assigned_volunteer = None # Ensures that the status gets to assigned if we set a volunteer # and the status was one that doesn't need a volunteer # This needs to happen after the clearing of the volunteer # when switching to a status without volunteer so there is no # volunteer and we don't update the status if (self.assigned_volunteer and self.action_status in self.STATUSES_WITHOUT_ASSIGNED_VOLUNTEER): self.action_status = ActionStatus.ASSIGNED # Track the contact date when setting the status # to one implying that contact would have happened if (self.action_status in (ActionStatus.ONGOING, ActionStatus.COMPLETED, ActionStatus.COULDNT_COMPLETE) and not self.volunteer_made_contact_on): self.volunteer_made_contact_on = timezone.now() # Track other interesting dates if (self.action_status not in self.STATUSES_WITHOUT_ASSIGNED_VOLUNTEER and not self.assigned_date): self.assigned_date = timezone.now() # if (self.action_status in self.STATUSES_WITHOUT_ASSIGNED_VOLUNTEER and self.assigned_date): # self.assigned_date = None if (self.action_status == ActionStatus.COMPLETED and not self.completed_date): self.completed_date = timezone.now() # if (self.action_status != ActionStatus.COMPLETED and self.completed_date): # self.completed_date = None # Only for updates as it runs on a related field if self.pk is not None: # Update the status according to whether there are interested_volunteers or not if (self.action_status == ActionStatus.PENDING and self.interested_volunteers.count() > 0): self.action_status = ActionStatus.INTEREST if (self.action_status == ActionStatus.INTEREST and self.interested_volunteers.count() == 0): self.action_status = ActionStatus.PENDING super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields) # Needs to happen after the save so the M2M relation can be saved properly # when creating an action # Without waiting for the end of the transaction, the volunteer # doesn't actually gets saved transaction.on_commit(self.save_assigned_volunteer, using=using) def save_assigned_volunteer(self): """ Ensures the assigned_volunteer is within the list of interested volunteers """ if (self.assigned_volunteer and not self.assigned_volunteer in self.interested_volunteers.all()): self.interested_volunteers.add(self.assigned_volunteer) def register_interest_from(self, volunteer): if volunteer not in self.interested_volunteers.all(): self.interested_volunteers.add(volunteer) self.save() def withdraw_interest_from(self, volunteer): if volunteer in self.interested_volunteers.all(): self.interested_volunteers.remove(volunteer) self.save() @property def ward(self): return self.resident.ward @property def description(self): return f"Help with {self.help_type} around {self.ward}" @property def description_with_date(self): return f"{self.description} by {self.requested_datetime.strftime('%d %b')}" @property def is_pending(self): return self.action_status == ActionStatus.PENDING @property def is_ongoing(self): return self.action_status == ActionStatus.ONGOING @property def has_interest(self): return self.action_status == ActionStatus.INTEREST @property def is_assigned(self): return self.assigned_volunteer is not None @property def is_completed(self): return self.action_status == ActionStatus.COMPLETED @property def is_failed(self): return self.action_status == ActionStatus.COULDNT_COMPLETE @property def can_reveal_private_information(self): return not ( self.action_status == ActionStatus.INTEREST or self.action_status == ActionStatus.PENDING) @property def can_give_feedback(self): return self.action_status == ActionStatus.ASSIGNED or self.action_status == ActionStatus.ONGOING @property def potential_volunteers(self): return user_models.Volunteer.objects \ .filter(wards__id=self.ward.id) \ .filter(help_types__id=self.help_type.id) \ .all() def get_absolute_url(self): from django.urls import reverse return reverse('actions:detail', kwargs={'action_uuid': self.action_uuid}) def __str__(self): return f"Action {self.id} - {self.resident.full_name}"
class Offering(core_models.UuidMixin, core_models.NameMixin, common_mixins.ProductCodeMixin, common_mixins.UnitPriceMixin, structure_models.StructureLoggableMixin, TimeStampedModel): class Meta: ordering = ['-created'] verbose_name = _('Request') verbose_name_plural = _('Requests') class Permissions(object): customer_path = 'project__customer' project_path = 'project' class States(object): REQUESTED = 'requested' OK = 'ok' TERMINATED = 'terminated' CHOICES = ((REQUESTED, _('Requested')), (OK, _('OK')), (TERMINATED, _('Terminated'))) template = models.ForeignKey('OfferingTemplate', on_delete=models.PROTECT) issue = models.ForeignKey(Issue, null=True, on_delete=models.PROTECT) project = models.ForeignKey(structure_models.Project, null=True, on_delete=models.PROTECT) state = models.CharField(default=States.REQUESTED, max_length=30, choices=States.CHOICES) report = core_fields.JSONField(blank=True) terminated_at = models.DateTimeField(editable=False, blank=True, null=True) tracker = FieldTracker() def get_backend(self): backend.get_active_backend() def get_log_fields(self): return super(Offering, self).get_log_fields() + ('state', ) @property def type(self): return self.template.name @property def type_label(self): return self.template.config.get('label', None) @classmethod def get_url_name(cls): return 'support-offering' def __str__(self): return '{}: {}'.format(self.type_label or self.name, self.state) @classmethod def get_scope_type(cls): return 'Support.Offering' def _get_log_context(self, entity_name): context = super(Offering, self)._get_log_context(entity_name) context['resource_type'] = self.get_scope_type() context['resource_uuid'] = self.uuid.hex return context @property def config(self): return self.template.config if self.template else {}
class ActivitySubmission(AuditModel): """ Activity information on a Well submitted by a user. """ filing_number = models.AutoField(primary_key=True) activity_submission_guid = models.UUIDField(primary_key=False, default=uuid.uuid4, editable=False) well_tag_number = models.ForeignKey(Well, db_column='well_tag_number', on_delete=models.CASCADE, blank=True, null=True) well_activity_type = models.ForeignKey(WellActivityCode, db_column='well_activity_code', on_delete=models.CASCADE, verbose_name='Type of Work') well_class = models.ForeignKey(WellClassCode, null=True, db_column='well_class_code', on_delete=models.CASCADE, verbose_name='Well Class') well_subclass = models.ForeignKey(WellSubclassCode, db_column='well_subclass_guid', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Well Subclass') intended_water_use = models.ForeignKey(IntendedWaterUseCode, db_column='intended_water_use_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Intended Water Use') driller_responsible = models.ForeignKey(Driller, db_column='driller_responsible_guid', on_delete=models.CASCADE, verbose_name='Person Responsible for Drilling') driller_name = models.CharField(max_length=200, blank=True, verbose_name='Name of Person Who Did the Work') consultant_name = models.CharField(max_length=200, blank=True, verbose_name='Consultant Name') consultant_company = models.CharField(max_length=200, blank=True, verbose_name='Consultant Company') work_start_date = models.DateField(verbose_name='Work Start Date') work_end_date = models.DateField(verbose_name='Work End Date') owner_full_name = models.CharField(max_length=200, verbose_name='Owner Name') owner_mailing_address = models.CharField(max_length=100, verbose_name='Mailing Address') owner_city = models.CharField(max_length=100, verbose_name='Town/City') owner_province_state = models.ForeignKey(ProvinceStateCode, db_column='province_state_code', on_delete=models.CASCADE, verbose_name='Province') owner_postal_code = models.CharField(max_length=10, blank=True, verbose_name='Postal Code') street_address = models.CharField(max_length=100, blank=True, verbose_name='Street Address') city = models.CharField(max_length=50, blank=True, verbose_name='Town/City') legal_lot = models.CharField(max_length=10, blank=True, verbose_name='Lot') legal_plan = models.CharField(max_length=20, blank=True, verbose_name='Plan') legal_district_lot = models.CharField(max_length=20, blank=True, verbose_name='District Lot') legal_block = models.CharField(max_length=10, blank=True, verbose_name='Block') legal_section = models.CharField(max_length=10, blank=True, verbose_name='Section') legal_township = models.CharField(max_length=20, blank=True, verbose_name='Township') legal_range = models.CharField(max_length=10, blank=True, verbose_name='Range') land_district = models.ForeignKey(LandDistrictCode, db_column='land_district_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Land District') legal_pid = models.PositiveIntegerField(blank=True, null=True, verbose_name='PID') well_location_description = models.CharField(max_length=500, blank=True, verbose_name='Well Location Description') identification_plate_number = models.PositiveIntegerField(blank=True, null=True, verbose_name='Identification Plate Number') well_plate_attached = models.CharField(max_length=500, blank=True, verbose_name='Well Identification Plate Is Attached') latitude = models.DecimalField(max_digits=8, decimal_places=6, blank=True, null=True) longitude = models.DecimalField(max_digits=9, decimal_places=6, blank=True, null=True) ground_elevation = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, verbose_name='Ground Elevation') ground_elevation_method = models.ForeignKey(GroundElevationMethodCode, db_column='ground_elevation_method_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Elevation Determined By') drilling_method = models.ForeignKey(DrillingMethodCode, db_column='drilling_method_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Drilling Method') other_drilling_method = models.CharField(max_length=50, blank=True, verbose_name='Specify Other Drilling Method') well_orientation = models.BooleanField(default=True, verbose_name='Orientation of Well', choices=((True, 'vertical'), (False, 'horizontal'))) water_supply_system_name = models.CharField(max_length=50, blank=True, verbose_name='Water Supply System Name') water_supply_system_well_name = models.CharField(max_length=50, blank=True, verbose_name='Water Supply System Well Name') surface_seal_material = models.ForeignKey(SurfaceSealMaterialCode, db_column='surface_seal_material_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Surface Seal Material') surface_seal_depth = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True, verbose_name='Surface Seal Depth') surface_seal_thickness = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Surface Seal Thickness', validators=[MinValueValidator(Decimal('1.00'))]) surface_seal_method = models.ForeignKey(SurfaceSealMethodCode, db_column='surface_seal_method_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Surface Seal Installation Method') backfill_above_surface_seal = models.CharField(max_length=250, blank=True, verbose_name='Backfill Material Above Surface Seal') backfill_above_surface_seal_depth = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Backfill Depth') liner_material = models.ForeignKey(LinerMaterialCode, db_column='liner_material_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Liner Material') liner_diameter = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Liner Diameter', validators=[MinValueValidator(Decimal('0.00'))]) liner_thickness = models.DecimalField(max_digits=5, decimal_places=3, blank=True, null=True, verbose_name='Liner Thickness', validators=[MinValueValidator(Decimal('0.00'))]) liner_from = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Liner From', validators=[MinValueValidator(Decimal('0.00'))]) liner_to = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Liner To', validators=[MinValueValidator(Decimal('0.01'))]) screen_intake_method = models.ForeignKey(ScreenIntakeMethodCode, db_column='screen_intake_method_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Intake') screen_type = models.ForeignKey(ScreenTypeCode, db_column='screen_type_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Type') screen_material = models.ForeignKey(ScreenMaterialCode, db_column='screen_material_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Material') other_screen_material = models.CharField(max_length=50, blank=True, verbose_name='Specify Other Screen Material') screen_opening = models.ForeignKey(ScreenOpeningCode, db_column='screen_opening_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Opening') screen_bottom = models.ForeignKey(ScreenBottomCode, db_column='screen_bottom_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Bottom') other_screen_bottom = models.CharField(max_length=50, blank=True, verbose_name='Specify Other Screen Bottom') filter_pack_from = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Filter Pack From', validators=[MinValueValidator(Decimal('0.00'))]) filter_pack_to = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Filter Pack To', validators=[MinValueValidator(Decimal('0.01'))]) filter_pack_thickness = models.DecimalField(max_digits=5, decimal_places=3, blank=True, null=True, verbose_name='Filter Pack Thickness', validators=[MinValueValidator(Decimal('0.00'))]) filter_pack_material = models.ForeignKey(FilterPackMaterialCode, db_column='filter_pack_material_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Filter Pack Material') filter_pack_material_size = models.ForeignKey(FilterPackMaterialSizeCode, db_column='filter_pack_material_size_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Filter Pack Material Size') development_method = models.ForeignKey(DevelopmentMethodCode, db_column='development_method_code', on_delete=models.CASCADE, blank=True, null=True, verbose_name='Development Method') development_hours = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True, verbose_name='Development Total Duration', validators=[MinValueValidator(Decimal('0.00'))]) development_notes = models.CharField(max_length=255, blank=True, verbose_name='Development Notes') water_quality_characteristics = models.ManyToManyField(WaterQualityCharacteristic, db_table='activity_submission_water_quality', blank=True, verbose_name='Obvious Water Quality Characteristics') water_quality_colour = models.CharField(max_length=60, blank=True, verbose_name='Water Quality Colour') water_quality_odour = models.CharField(max_length=60, blank=True, verbose_name='Water Quality Odour') total_depth_drilled = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Total Depth Drilled') finished_well_depth = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Finished Well Depth') final_casing_stick_up = models.DecimalField(max_digits=5, decimal_places=3, blank=True, null=True, verbose_name='Final Casing Stick Up') bedrock_depth = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Depth to Bedrock') static_water_level = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Static Water Level (BTOC)') well_yield = models.DecimalField(max_digits=8, decimal_places=3, blank=True, null=True, verbose_name='Estimated Well Yield') artesian_flow = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True, verbose_name='Artesian Flow') artesian_pressure = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True, verbose_name='Artesian Pressure') well_cap_type = models.CharField(max_length=40, blank=True, verbose_name='Well Cap Type') well_disinfected = models.BooleanField(default=False, verbose_name='Well Disinfected?', choices=((False, 'No'), (True, 'Yes'))) comments = models.CharField(max_length=3000, blank=True) alternative_specs_submitted = models.BooleanField(default=False, verbose_name='Alternative specs submitted (if required)') well_yield_unit = models.ForeignKey(WellYieldUnitCode, db_column='well_yield_unit_code', on_delete=models.CASCADE, blank=True, null=True) diameter = models.CharField(max_length=9, blank=True) #want to be integer in future tracker = FieldTracker() def create_well(self): w = Well(well_class = self.well_class) w.well_subclass = self.well_subclass w.intended_water_use = self.intended_water_use w.owner_full_name = self.owner_full_name w.owner_mailing_address = self.owner_mailing_address w.owner_city = self.owner_city w.owner_province_state = self.owner_province_state w.owner_postal_code = self.owner_postal_code w.street_address = self.street_address w.city = self.city w.legal_lot = self.legal_lot w.legal_plan = self.legal_plan w.legal_district_lot = self.legal_district_lot w.legal_block = self.legal_block w.legal_section = self.legal_section w.legal_township = self.legal_township w.legal_range = self.legal_range w.land_district = self.land_district w.legal_pid = self.legal_pid w.well_location_description = self.well_location_description w.identification_plate_number = self.identification_plate_number w.latitude = self.latitude w.longitude = self.longitude w.ground_elevation = self.ground_elevation w.ground_elevation_method = self.ground_elevation_method w.drilling_method = self.drilling_method w.other_drilling_method = self.other_drilling_method w.well_orientation = self.well_orientation w.surface_seal_material = self.surface_seal_material w.surface_seal_depth = self.surface_seal_depth w.surface_seal_thickness = self.surface_seal_thickness w.surface_seal_method = self.surface_seal_method w.backfill_above_surface_seal = self.backfill_above_surface_seal w.backfill_above_surface_seal_depth = self.backfill_above_surface_seal_depth w.liner_material = self.liner_material w.liner_diameter = self.liner_diameter w.liner_thickness = self.liner_thickness w.liner_from = self.liner_from w.liner_to = self.liner_to w.screen_intake = self.screen_intake w.screen_type = self.screen_type w.screen_material = self.screen_material w.other_screen_material = self.other_screen_material w.screen_opening = self.screen_opening w.screen_bottom = self.screen_bottom w.other_screen_bottom = self.other_screen_bottom w.filter_pack_from = self.filter_pack_from w.filter_pack_to = self.filter_pack_to w.filter_pack_thickness = self.filter_pack_thickness w.filter_pack_material = self.filter_pack_material w.filter_pack_material_size = self.filter_pack_material_size w.development_method = self.development_method w.development_hours = self.development_hours w.development_notes = self.development_notes w.water_quality_colour = self.water_quality_colour w.water_quality_odour = self.water_quality_odour w.total_depth_drilled = self.total_depth_drilled w.finished_well_depth = self.finished_well_depth w.final_casing_stick_up = self.final_casing_stick_up w.bedrock_depth = self.bedrock_depth w._water_level = self.static_water_level w.well_yield = self.well_yield w.artestian_flow = self.artestian_flow w.artestian_pressure = self.artestian_pressure w.well_cap_type = self.well_cap_type w.well_disinfected = self.well_disinfected w.comments = self.comments w.alternative_specs_submitted = self.alternative_specs_submitted #TODO return w; class Meta: db_table = 'activity_submission' def __str__(self): if self.filing_number: return '%s %d %s %s' % (self.activity_submission_guid, self.filing_number, self.well_activity_type.well_activity_code, self.street_address) else: return '%s %s' % (self.activity_submission_guid, self.street_address)
class SiteSettings(models.Model): """customized settings for this instance""" name = models.CharField(default="BookWyrm", max_length=100) instance_tagline = models.CharField( max_length=150, default="Social Reading and Reviewing" ) instance_description = models.TextField(default="This instance has no description.") instance_short_description = models.CharField(max_length=255, blank=True, null=True) # about page registration_closed_text = models.TextField( default="We aren't taking new users at this time. You can find an open " 'instance at <a href="https://joinbookwyrm.com/instances">' "joinbookwyrm.com/instances</a>." ) invite_request_text = models.TextField( default="If your request is approved, you will receive an email with a " "registration link." ) code_of_conduct = models.TextField(default="Add a code of conduct here.") privacy_policy = models.TextField(default="Add a privacy policy here.") # registration allow_registration = models.BooleanField(default=True) allow_invite_requests = models.BooleanField(default=True) require_confirm_email = models.BooleanField(default=True) # images logo = models.ImageField(upload_to="logos/", null=True, blank=True) logo_small = models.ImageField(upload_to="logos/", null=True, blank=True) favicon = models.ImageField(upload_to="logos/", null=True, blank=True) preview_image = models.ImageField( upload_to="previews/logos/", null=True, blank=True ) # footer support_link = models.CharField(max_length=255, null=True, blank=True) support_title = models.CharField(max_length=100, null=True, blank=True) admin_email = models.EmailField(max_length=255, null=True, blank=True) footer_item = models.TextField(null=True, blank=True) field_tracker = FieldTracker(fields=["name", "instance_tagline", "logo"]) @classmethod def get(cls): """gets the site settings db entry or defaults""" try: return cls.objects.get(id=1) except cls.DoesNotExist: default_settings = SiteSettings(id=1) default_settings.save() return default_settings @property def logo_url(self): """helper to build the logo url""" return self.get_url("logo", "images/logo.png") @property def logo_small_url(self): """helper to build the logo url""" return self.get_url("logo_small", "images/logo-small.png") @property def favicon_url(self): """helper to build the logo url""" return self.get_url("favicon", "images/favicon.png") def get_url(self, field, default_path): """get a media url or a default static path""" uploaded = getattr(self, field, None) if uploaded: return get_absolute_url(uploaded) return urljoin(STATIC_FULL_URL, default_path)
class Comment( core_models.UuidMixin, core_models.BackendModelMixin, TimeStampedModel, core_models.StateMixin, ): class Meta: ordering = ['-created'] unique_together = ('backend_id', 'issue') class Permissions: customer_path = 'issue__customer' project_path = 'issue__project' issue = models.ForeignKey( on_delete=models.CASCADE, to=Issue, related_name='comments' ) author = models.ForeignKey( on_delete=models.CASCADE, to=SupportUser, related_name='comments' ) description = models.TextField() is_public = models.BooleanField(default=True) backend_id = models.CharField(max_length=255, blank=True, null=True) tracker = FieldTracker() def clean_message(self, message): """ Extracts comment message from JIRA comment which contains user's info in its body. """ match = re.search(r'^(\[.*?\]\:\s)', message) return message.replace(match.group(0), '') if match else message def prepare_message(self): """ Prepends user info to the comment description to display comment author in JIRA. User info format - '[user.full_name user.civil_number]: '. """ prefix = self.author.name # User is optional user = self.author.user if user: prefix = user.full_name or user.username if user.civil_number: prefix += ' ' + user.civil_number return '[%s]: %s' % (prefix, self.description) def update_message(self, message): self.description = self.clean_message(message) @classmethod def get_url_name(cls): return 'support-comment' @classmethod def get_backend_fields(cls): return super(Comment, cls).get_backend_fields() + ( 'issue', 'author', 'description', 'is_public', 'backend_id', ) def __str__(self): return self.description[:50]
class RegistrationAuthority(registryGroup): """ A registration authority is a proxy group that describes a governance process for "standardising" metadata. """ template = "aristotle_mdr/registrationAuthority.html" locked_state = models.IntegerField( choices=STATES, default=STATES.candidate ) public_state = models.IntegerField( choices=STATES, default=STATES.recorded ) registrars = models.ManyToManyField( User, blank=True, related_name='registrar_in', verbose_name=_('Registrars') ) # The below text fields allow for brief descriptions of the context of each # state for a particular Registration Authority # For example: # For a particular Registration Authority standard may mean" # "Approved by a simple majority of the standing council of metadata # standardisation" # While "Preferred Standard" may mean: # "Approved by a two-thirds majority of the standing council of metadata # standardisation" notprogressed = models.TextField(blank=True) incomplete = models.TextField(blank=True) candidate = models.TextField(blank=True) recorded = models.TextField(blank=True) qualified = models.TextField(blank=True) standard = models.TextField(blank=True) preferred = models.TextField(blank=True) superseded = models.TextField(blank=True) retired = models.TextField(blank=True) tracker = FieldTracker() class Meta: verbose_name_plural = _("Registration Authorities") def get_absolute_url(self): return url_slugify_registration_authoritity(self) def can_view(self, user): return True @property def unlocked_states(self): return range(STATES.notprogressed, self.locked_state) @property def locked_states(self): return range(self.locked_state, self.public_state) @property def public_states(self): return range(self.public_state, STATES.retired + 1) def statusDescriptions(self): descriptions = [ self.notprogressed, self.incomplete, self.candidate, self.recorded, self.qualified, self.standard, self.preferred, self.superseded, self.retired ] unlocked = [ (i, STATES[i], descriptions[i]) for i in self.unlocked_states ] locked = [ (i, STATES[i], descriptions[i]) for i in self.locked_states ] public = [ (i, STATES[i], descriptions[i]) for i in self.public_states ] return ( ('unlocked', unlocked), ('locked', locked), ('public', public) ) def cascaded_register(self, item, state, user, *args, **kwargs): if not perms.user_can_change_status(user, item): # Return a failure as this item isn't allowed return {'success': [], 'failed': [item] + item.registry_cascade_items} revision_message = _( "Cascade registration of item '%(name)s' (id:%(iid)s)\n" ) % { 'name': item.name, 'iid': item.id } revision_message = revision_message + kwargs.get('changeDetails', "") seen_items = {'success': [], 'failed': []} with transaction.atomic(), reversion.revisions.create_revision(): reversion.revisions.set_user(user) reversion.revisions.set_comment(revision_message) for child_item in [item] + item.registry_cascade_items: self._register( child_item, state, user, *args, **kwargs ) seen_items['success'] = seen_items['success'] + [child_item] return seen_items def register(self, item, state, user, *args, **kwargs): if not perms.user_can_change_status(user, item): # Return a failure as this item isn't allowed return {'success': [], 'failed': [item]} revision_message = kwargs.get('changeDetails', "") with transaction.atomic(), reversion.revisions.create_revision(): reversion.revisions.set_user(user) reversion.revisions.set_comment(revision_message) self._register(item, state, user, *args, **kwargs) return {'success': [item], 'failed': []} def _register(self, item, state, user, *args, **kwargs): changeDetails = kwargs.get('changeDetails', "") # If registrationDate is None (like from a form), override it with # todays date. registrationDate = kwargs.get('registrationDate', None) \ or timezone.now().date() until_date = kwargs.get('until_date', None) Status.objects.create( concept=item, registrationAuthority=self, registrationDate=registrationDate, state=state, changeDetails=changeDetails, until_date=until_date ) def giveRoleToUser(self, role, user): if role == 'registrar': self.registrars.add(user) if role == "manager": self.managers.add(user) def removeRoleFromUser(self, role, user): if role == 'registrar': self.registrars.remove(user) if role == "manager": self.managers.remove(user)
class ActionPoint(TimeStampedModel): MODULE_CHOICES = Choices( ('t2f', _('Trip Management')), ('tpm', 'Third Party Monitoring'), ('audit', _('Auditor Portal')), ) STATUSES = Choices( ('open', _('Open')), ('completed', _('Completed')), ) STATUSES_DATES = { STATUSES.open: 'created', STATUSES.completed: 'date_of_completion' } KEY_EVENTS = Choices( ('status_update', _('Status Update')), ('reassign', _('Reassign')), ) author = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='created_action_points', verbose_name=_('Author'), on_delete=models.CASCADE, ) assigned_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='+', verbose_name=_('Assigned By'), on_delete=models.CASCADE, ) assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='assigned_action_points', verbose_name=_('Assigned To'), on_delete=models.CASCADE, ) status = FSMField(verbose_name=_('Status'), max_length=10, choices=STATUSES, default=STATUSES.open, protected=True) description = models.TextField(verbose_name=_('Description')) due_date = models.DateField(verbose_name=_('Due Date'), blank=True, null=True) high_priority = models.BooleanField(default=False, verbose_name=_('High Priority')) section = models.ForeignKey('reports.Sector', verbose_name=_('Section'), blank=True, null=True, on_delete=models.CASCADE, ) office = models.ForeignKey('users.Office', verbose_name=_('Office'), blank=True, null=True, on_delete=models.CASCADE, ) location = models.ForeignKey('locations.Location', verbose_name=_('Location'), blank=True, null=True, on_delete=models.CASCADE, ) partner = models.ForeignKey('partners.PartnerOrganization', verbose_name=_('Partner'), blank=True, null=True, on_delete=models.CASCADE, ) cp_output = models.ForeignKey('reports.Result', verbose_name=_('CP Output'), blank=True, null=True, on_delete=models.CASCADE, ) intervention = models.ForeignKey('partners.Intervention', verbose_name=_('PD/SSFA'), blank=True, null=True, on_delete=models.CASCADE, ) engagement = models.ForeignKey('audit.Engagement', verbose_name=_('Engagement'), blank=True, null=True, related_name='action_points', on_delete=models.CASCADE, ) tpm_activity = models.ForeignKey('tpm.TPMActivity', verbose_name=_('TPM Activity'), blank=True, null=True, related_name='action_points', on_delete=models.CASCADE, ) travel = models.ForeignKey('t2f.Travel', verbose_name=_('Travel'), blank=True, null=True, on_delete=models.CASCADE, ) date_of_completion = MonitorField(verbose_name=_('Date Action Point Completed'), null=True, blank=True, default=None, monitor='status', when=[STATUSES.completed]) comments = GenericRelation('django_comments.Comment', object_id_field='object_pk') history = GenericRelation('snapshot.Activity', object_id_field='target_object_id', content_type_field='target_content_type') tracker = FieldTracker(fields=['assigned_to']) class Meta: ordering = ('id', ) verbose_name = _('Action Point') verbose_name_plural = _('Action Points') @property def engagement_subclass(self): return self.engagement.get_subclass() if self.engagement else None @property def related_object(self): return self.engagement_subclass or self.tpm_activity or self.travel @property def related_module(self): if self.engagement: return self.MODULE_CHOICES.audit if self.tpm_activity: return self.MODULE_CHOICES.tpm if self.travel: return self.MODULE_CHOICES.t2f return None @property def reference_number(self): return '{}/{}/{}/APD'.format( connection.tenant.country_short_code or '', self.created.year, self.id, ) def get_object_url(self, **kwargs): return build_frontend_url('apd', 'action-points', 'detail', self.id, **kwargs) @property def status_date(self): return getattr(self, self.STATUSES_DATES[self.status]) def __str__(self): return self.reference_number def get_meaningful_history(self): return self.history.filter( models.Q(action=Activity.CREATE) | models.Q(models.Q(action=Activity.UPDATE), ~models.Q(change={})) ) def snapshot_additional_data(self, diff): key_events = [] if 'status' in diff: key_events.append(self.KEY_EVENTS.status_update) if 'assigned_to' in diff: key_events.append(self.KEY_EVENTS.reassign) return {'key_events': key_events} @classmethod def get_snapshot_action_display(cls, activity): key_events = activity.data.get('key_events') if key_events: if cls.KEY_EVENTS.status_update in key_events: return _('Changed status to {}').format(cls.STATUSES[activity.change['status']['after']]) elif cls.KEY_EVENTS.reassign in key_events: return _('Reassigned to {}').format( get_user_model().objects.get(pk=activity.change['assigned_to']['after']).get_full_name() ) return activity.get_action_display() def get_mail_context(self, user=None, include_token=False): return { 'person_responsible': self.assigned_to.get_full_name(), 'assigned_by': self.assigned_by.get_full_name(), 'reference_number': self.reference_number, 'partner': self.partner.name if self.partner else '', 'description': self.description, 'due_date': self.due_date.strftime('%d %b %Y') if self.due_date else '', 'object_url': self.get_object_url(user=user, include_token=include_token), } def send_email(self, recipient, template_name, additional_context=None): context = { 'environment': get_environment(), 'action_point': self.get_mail_context(user=recipient), 'recipient': recipient.get_full_name(), } context.update(additional_context or {}) notification = Notification.objects.create( sender=self, recipients=[recipient.email], template_name=template_name, template_data=context ) notification.send_notification() def _do_complete(self): self.send_email(self.assigned_by, 'action_points/action_point/completed') @transition(status, source=STATUSES.open, target=STATUSES.completed, permission=has_action_permission(action='complete'), conditions=[ ActionPointCompleteActionsTakenCheck.as_condition() ]) def complete(self): self._do_complete()
class Workgroup(registryGroup): """ A workgroup is a collection of associated users given control to work on a specific piece of work. Usually this work will be the creation of a specific collection of objects, such as data elements, for a specific topic. Workgroup owners may choose to 'archive' a workgroup. All content remains visible, but the workgroup is hidden in lists and new items cannot be created in that workgroup. """ template = "aristotle_mdr/workgroup.html" archived = models.BooleanField( default=False, help_text=_("Archived workgroups can no longer have new items or " "discussions created within them."), verbose_name=_('Archived'), ) viewers = models.ManyToManyField( User, blank=True, related_name='viewer_in', verbose_name=_('Viewers') ) submitters = models.ManyToManyField( User, blank=True, related_name='submitter_in', verbose_name=_('Submitters') ) stewards = models.ManyToManyField( User, blank=True, related_name='steward_in', verbose_name=_('Stewards') ) roles = { 'submitter': _("Submitter"), 'viewer': _("Viewer"), 'steward': _("Steward"), 'manager': _("Manager") } tracker = FieldTracker() def get_absolute_url(self): return url_slugify_workgroup(self) @property def members(self): return self.viewers.all() \ | self.submitters.all() \ | self.stewards.all() \ | self.managers.all() def can_view(self, user): return self.members.filter(pk=user.pk).exists() @property def classedItems(self): # Convenience class as we can't call functions in templates return self.items.select_subclasses() def giveRoleToUser(self, role, user): if role == "manager": self.managers.add(user) if role == "viewer": self.viewers.add(user) if role == "submitter": self.submitters.add(user) if role == "steward": self.stewards.add(user) self.save() def removeRoleFromUser(self, role, user): if role == "manager": self.managers.remove(user) if role == "viewer": self.viewers.remove(user) if role == "submitter": self.submitters.remove(user) if role == "steward": self.stewards.remove(user) self.save() def removeUser(self, user): self.viewers.remove(user) self.submitters.remove(user) self.stewards.remove(user) self.managers.remove(user)
class Project(core_models.DescribableMixin, core_models.UuidMixin, core_models.NameMixin, core_models.DescendantMixin, quotas_models.QuotaModelMixin, LoggableMixin, TimeStampedModel, StructureModel): class Permissions(object): customer_path = 'customer' project_path = 'self' project_group_path = 'project_groups' QUOTAS_NAMES = ['nc_resource_count', 'nc_service_project_link_count'] GLOBAL_COUNT_QUOTA_NAME = 'nc_global_project_count' customer = models.ForeignKey(Customer, related_name='projects', on_delete=models.PROTECT) tracker = FieldTracker() # XXX: Hack for gcloud and logging @property def project_group(self): return self.project_groups.first() @property def full_name(self): project_group = self.project_group name = (project_group.name + ' / ' if project_group else '') + self.name return name def add_user(self, user, role_type): UserGroup = get_user_model().groups.through with transaction.atomic(): role = self.roles.get(role_type=role_type) membership, created = UserGroup.objects.get_or_create( user=user, group=role.permission_group, ) if created: structure_role_granted.send( sender=Project, structure=self, user=user, role=role_type, ) return membership, created def remove_user(self, user, role_type=None): UserGroup = get_user_model().groups.through with transaction.atomic(): memberships = UserGroup.objects.filter( group__projectrole__project=self, user=user, ) if role_type is not None: memberships = memberships.filter( group__projectrole__role_type=role_type) self.remove_memberships(memberships) def remove_all_users(self): UserGroup = get_user_model().groups.through with transaction.atomic(): memberships = UserGroup.objects.filter( group__projectrole__project=self) self.remove_memberships(memberships) def remove_memberships(self, memberships): for membership in memberships.iterator(): role = membership.group.projectrole structure_role_revoked.send( sender=Project, structure=self, user=membership.user, role=role.role_type, ) membership.delete() def has_user(self, user, role_type=None): queryset = self.roles.filter(permission_group__user=user) if role_type is not None: queryset = queryset.filter(role_type=role_type) return queryset.exists() def get_users(self): return get_user_model().objects.filter( groups__projectrole__project=self) def __str__(self): return '%(name)s | %(customer)s' % { 'name': self.name, 'customer': self.customer.name } def can_user_update_quotas(self, user): return user.is_staff def get_log_fields(self): return ('uuid', 'customer', 'name', 'project_group') @classmethod def get_permitted_objects_uuids(cls, user): return { 'project_uuid': filter_queryset_for_user(cls.objects.all(), user).values_list('uuid', flat=True) } def get_parents(self): return [self.customer] def get_links(self): """ Get all service project links connected to current project """ return [ link for model in SupportedServices.get_service_models().values() for link in model['service_project_link'].objects.filter( project=self) ]
class Instance(TenantQuotaMixin, structure_models.VirtualMachine): class RuntimeStates(object): # All possible OpenStack Instance states on backend. # See https://docs.openstack.org/developer/nova/vmstates.html ACTIVE = 'ACTIVE' BUILDING = 'BUILDING' DELETED = 'DELETED' SOFT_DELETED = 'SOFT_DELETED' ERROR = 'ERROR' UNKNOWN = 'UNKNOWN' HARD_REBOOT = 'HARD_REBOOT' REBOOT = 'REBOOT' REBUILD = 'REBUILD' PASSWORD = '******' PAUSED = 'PAUSED' RESCUED = 'RESCUED' RESIZED = 'RESIZED' REVERT_RESIZE = 'REVERT_RESIZE' SHUTOFF = 'SHUTOFF' STOPPED = 'STOPPED' SUSPENDED = 'SUSPENDED' VERIFY_RESIZE = 'VERIFY_RESIZE' # backend_id is nullable on purpose, otherwise # it wouldn't be possible to put a unique constraint on it backend_id = models.CharField(max_length=255, blank=True, null=True) service_project_link = models.ForeignKey(OpenStackTenantServiceProjectLink, related_name='instances', on_delete=models.PROTECT) flavor_name = models.CharField(max_length=255, blank=True) flavor_disk = models.PositiveIntegerField( default=0, help_text=_('Flavor disk size in MiB')) security_groups = models.ManyToManyField(SecurityGroup, related_name='instances') # TODO: Move this fields to resource model. action = models.CharField(max_length=50, blank=True) action_details = JSONField(default=dict) subnets = models.ManyToManyField('SubNet', through='InternalIP') tracker = FieldTracker() class Meta(object): unique_together = ('service_project_link', 'backend_id') @property def external_ips(self): return list(self.floating_ips.values_list('address', flat=True)) @property def internal_ips(self): return list(self.internal_ips_set.values_list('ip4_address', flat=True)) @property def size(self): return self.volumes.aggregate(models.Sum('size'))['size__sum'] @classmethod def get_url_name(cls): return 'openstacktenant-instance' def get_log_fields(self): return ( 'uuid', 'name', 'type', 'service_project_link', 'ram', 'cores', ) def detect_coordinates(self): settings = self.service_project_link.service.settings options = settings.options or {} if 'latitude' in options and 'longitude' in options: return structure_utils.Coordinates(latitude=settings['latitude'], longitude=settings['longitude']) else: hostname = urlparse(settings.backend_url).hostname if hostname: return structure_utils.get_coordinates_by_ip(hostname) def get_quota_deltas(self): return { TenantQuotas.instances: 1, TenantQuotas.ram: self.ram, TenantQuotas.vcpu: self.cores, } @property def floating_ips(self): return FloatingIP.objects.filter(internal_ip__instance=self) @classmethod def get_backend_fields(cls): return super(Instance, cls).get_backend_fields() + ( 'flavor_name', 'flavor_disk', 'ram', 'cores', 'disk', 'runtime_state') @classmethod def get_online_state(cls): return Instance.RuntimeStates.ACTIVE @classmethod def get_offline_state(cls): return Instance.RuntimeStates.SHUTOFF
class ProjectGroup(core_models.UuidMixin, core_models.DescribableMixin, core_models.NameMixin, core_models.DescendantMixin, quotas_models.QuotaModelMixin, LoggableMixin, TimeStampedModel): """ Project groups are means to organize customer's projects into arbitrary sets. """ class Permissions(object): customer_path = 'customer' project_path = 'projects' project_group_path = 'self' customer = models.ForeignKey(Customer, related_name='project_groups', on_delete=models.PROTECT) projects = models.ManyToManyField(Project, related_name='project_groups') tracker = FieldTracker() GLOBAL_COUNT_QUOTA_NAME = 'nc_global_project_group_count' def __str__(self): return self.name def add_user(self, user, role_type): UserGroup = get_user_model().groups.through with transaction.atomic(): role = self.roles.get(role_type=role_type) membership, created = UserGroup.objects.get_or_create( user=user, group=role.permission_group, ) if created: structure_role_granted.send( sender=ProjectGroup, structure=self, user=user, role=role_type, ) return membership, created def remove_user(self, user, role_type=None): UserGroup = get_user_model().groups.through with transaction.atomic(): memberships = UserGroup.objects.filter( group__projectgrouprole__project_group=self, user=user, ) if role_type is not None: memberships = memberships.filter( group__projectgrouprole__role_type=role_type) for membership in memberships.iterator(): role = membership.group.projectgrouprole structure_role_revoked.send( sender=ProjectGroup, structure=self, user=membership.user, role=role.role_type, ) membership.delete() def has_user(self, user, role_type=None): queryset = self.roles.filter(permission_group__user=user) if role_type is not None: queryset = queryset.filter(role_type=role_type) return queryset.exists() def get_log_fields(self): return ('uuid', 'customer', 'name') def get_parents(self): return [self.customer] @classmethod def get_permitted_objects_uuids(cls, user): return { 'project_group_uuid': filter_queryset_for_user(cls.objects.all(), user).values_list('uuid', flat=True) }
class CourseTeam(models.Model): """ This model represents team related info. .. no_pii: """ class Meta(object): app_label = "teams" team_id = models.CharField(max_length=255, unique=True) discussion_topic_id = models.CharField(max_length=255, unique=True) name = models.CharField(max_length=255, db_index=True) course_id = CourseKeyField(max_length=255, db_index=True) topic_id = models.CharField(max_length=255, db_index=True, blank=True) date_created = models.DateTimeField(auto_now_add=True) description = models.CharField(max_length=300) country = CountryField(blank=True) language = LanguageField( blank=True, help_text=ugettext_lazy( "Optional language the team uses as ISO 639-1 code."), ) last_activity_at = models.DateTimeField( db_index=True) # indexed for ordering users = models.ManyToManyField(User, db_index=True, related_name='teams', through='CourseTeamMembership') team_size = models.IntegerField(default=0, db_index=True) # indexed for ordering field_tracker = FieldTracker() # Don't emit changed events when these fields change. FIELD_BLACKLIST = ['last_activity_at', 'team_size'] @classmethod def create(cls, name, course_id, description, topic_id=None, country=None, language=None): """Create a complete CourseTeam object. Args: name (str): The name of the team to be created. course_id (str): The ID string of the course associated with this team. description (str): A description of the team. topic_id (str): An optional identifier for the topic the team formed around. country (str, optional): An optional country where the team is based, as ISO 3166-1 code. language (str, optional): An optional language which the team uses, as ISO 639-1 code. """ unique_id = uuid4().hex team_id = slugify(name)[0:20] + '-' + unique_id discussion_topic_id = unique_id course_team = cls( team_id=team_id, discussion_topic_id=discussion_topic_id, name=name, course_id=course_id, topic_id=topic_id if topic_id else '', description=description, country=country if country else '', language=language if language else '', last_activity_at=datetime.utcnow().replace(tzinfo=pytz.utc)) return course_team def __repr__(self): return "<CourseTeam team_id={0.team_id}>".format(self) def add_user(self, user): """Adds the given user to the CourseTeam.""" if not CourseEnrollment.is_enrolled(user, self.course_id): raise NotEnrolledInCourseForTeam if CourseTeamMembership.user_in_team_for_course(user, self.course_id): raise AlreadyOnTeamInCourse return CourseTeamMembership.objects.create(user=user, team=self) def reset_team_size(self): """Reset team_size to reflect the current membership count.""" self.team_size = CourseTeamMembership.objects.filter(team=self).count() self.save()
class Derivation(NotifyModel): """ A Django model representing a grammatical derivation. Each Derivation can host multiple DerivationStep chains, since multiple operation types can result in multiple branching paths at each individual DerivationStep. Each DerivationStep chain should either converge or crash, as determined by the last step in the chain. """ id = models.UUIDField(primary_key=True, default=uuid.uuid4) # Derivations are complete if all their possible chains have been # processed to a crash/convergence complete = models.BooleanField(default=False) @property def converged_count(self): return self.converged_steps.count() @property def crashed_count(self): return self.crashed_steps.count() @property def converged_chains(self): """ Returns all the DerivationStep chains associated with this Derivation which converged. :return: """ return Derivation.get_chains_from_steps(self.converged_steps.all()) @property def crashed_chains(self): """ Returns all the DerivationStep chains associated with this Derivation which converged. :return: """ return Derivation.get_chains_from_steps(self.crashed_steps.all()) @staticmethod def get_chains_from_steps(end_steps): """ From a given iterable of end DerivationSteps, retrieve all the corresponding full chains. :param end_steps: :return: """ chains = [] for end_step in end_steps: this_chain = [end_step] this_step = end_step # Each DerivationStep may have multiple `next_steps`, but only one # `previous_step`. If we follow the chain backward, we will get # back to the `first_step`. while this_step.previous_step: this_chain.append(this_step.previous_step) this_step = this_step.previous_step # We now have a chain from last step to first -- Reverse it and # append it to the list of all chains for this Derivation. this_chain.reverse() chains.append(this_chain) return chains #: Used for change notifications. Subscribers will only be alerted when a #: substantive change is made to a model instance. tracker = FieldTracker() #: Used for change notifications. Subscribers will receive the latest model #: data processed via this serializer. serializer_class = "grammar.serializers.DerivationSerializer" def __str__(self): if hasattr(self, "first_step"): return str(self.first_step) else: return "<Invalid Derivation>"
class CourseTeam(models.Model): """ This model represents team related info. .. no_pii: """ def __str__(self): return "{} in {}".format(self.name, self.course_id) def __repr__(self): return ( "<CourseTeam" " id={0.id}" " team_id={0.team_id}" " team_size={0.team_size}" " topic_id={0.topic_id}" " course_id={0.course_id}" ">" ).format(self) class Meta(object): app_label = "teams" team_id = models.SlugField(max_length=255, unique=True) discussion_topic_id = models.SlugField(max_length=255, unique=True) name = models.CharField(max_length=255, db_index=True) course_id = CourseKeyField(max_length=255, db_index=True) topic_id = models.CharField(max_length=255, db_index=True, blank=True) date_created = models.DateTimeField(auto_now_add=True) description = models.CharField(max_length=300) country = CountryField(blank=True) language = LanguageField( blank=True, help_text=ugettext_lazy("Optional language the team uses as ISO 639-1 code."), ) last_activity_at = models.DateTimeField(db_index=True) # indexed for ordering users = models.ManyToManyField(User, db_index=True, related_name='teams', through='CourseTeamMembership') team_size = models.IntegerField(default=0, db_index=True) # indexed for ordering field_tracker = FieldTracker() # This field would divide the teams into two mutually exclusive groups # If the team is org protected, the members in a team is enrolled into a degree bearing institution # If the team is not org protected, the members in a team is part of the general edX learning community # We need this exclusion for learner privacy protection organization_protected = models.BooleanField(default=False) # Don't emit changed events when these fields change. FIELD_BLACKLIST = ['last_activity_at', 'team_size'] @classmethod def create( cls, name, course_id, description, topic_id=None, country=None, language=None, organization_protected=False ): """Create a complete CourseTeam object. Args: name (str): The name of the team to be created. course_id (str): The ID string of the course associated with this team. description (str): A description of the team. topic_id (str): An optional identifier for the topic the team formed around. country (str, optional): An optional country where the team is based, as ISO 3166-1 code. language (str, optional): An optional language which the team uses, as ISO 639-1 code. organization_protected (bool, optional): specifies whether the team should only contain members who are in a organization context, or not """ unique_id = uuid4().hex team_id = slugify(name)[0:20] + '-' + unique_id discussion_topic_id = unique_id course_team = cls( team_id=team_id, discussion_topic_id=discussion_topic_id, name=name, course_id=course_id, topic_id=topic_id if topic_id else '', description=description, country=country if country else '', language=language if language else '', last_activity_at=datetime.utcnow().replace(tzinfo=pytz.utc), organization_protected=organization_protected ) return course_team def add_user(self, user): """Adds the given user to the CourseTeam.""" if not CourseEnrollment.is_enrolled(user, self.course_id): raise NotEnrolledInCourseForTeam if CourseTeamMembership.user_in_team_for_course(user, self.course_id): raise AlreadyOnTeamInCourse return CourseTeamMembership.objects.create( user=user, team=self ) def reset_team_size(self): """Reset team_size to reflect the current membership count.""" self.team_size = CourseTeamMembership.objects.filter(team=self).count() self.save()
class Account(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) username = models.CharField(max_length=255) foreign_id = models.CharField( max_length=255, null=True, unique=True, help_text='The foreign identifier from the datastore.') default_project = models.ForeignKey( 'karaage.Project', null=True, blank=True, on_delete=models.SET_NULL) date_created = models.DateField() date_deleted = models.DateField(null=True, blank=True) disk_quota = models.IntegerField(null=True, blank=True, help_text="In GB") shell = models.CharField(max_length=50) login_enabled = models.BooleanField(default=True) extra_data = JSONField( default={}, help_text='Datastore specific values should be stored in this field.') _tracker = FieldTracker() def __init__(self, *args, **kwargs): super(Account, self).__init__(*args, **kwargs) self._password = None class Meta: ordering = ['person', ] db_table = 'account' app_label = 'karaage' def __str__(self): return '%s' % self.username def get_absolute_url(self): return reverse('kg_account_detail', args=[self.pk]) @classmethod def create(cls, person, default_project): """Creates a Account (if needed) and activates person. """ ua = Account.objects.create( person=person, username=person.username, shell=settings.DEFAULT_SHELL, default_project=default_project, date_created=datetime.datetime.today()) if default_project is not None: person.add_group(default_project.group) return ua def project_list(self): return self.person.projects.all() def save(self, *args, **kwargs): created = self.pk is None # save the object super(Account, self).save(*args, **kwargs) if created: log.add( self.person, 'Account %s: Created' % self) for field in self._tracker.changed(): if field != "password": log.change( self.person, 'Account %s: Changed %s to %s' % (self, field, getattr(self, field))) # check if it was renamed if self._tracker.has_changed('username'): old_username = self._tracker.previous('username') if old_username is not None: new_username = self.username if self.date_deleted is None: from karaage.datastores import set_account_username set_account_username(self, old_username, new_username) log.change( self.person, 'Account %s: Changed username from %s to %s' % (self, old_username, new_username)) # check if deleted status changed if self._tracker.has_changed('date_deleted'): if self.date_deleted is not None: # account is deactivated from karaage.datastores import delete_account delete_account(self) log.delete( self.person, 'Account %s: Deactivated account' % self) # deleted else: # account is reactivated log.add( self.person, 'Account %s: Activated' % self) # makes sense to lock non-existant account if self.date_deleted is not None: self.login_enabled = False # update the datastore if self.date_deleted is None: from karaage.datastores import save_account save_account(self) if self._password is not None: from karaage.datastores import set_account_password set_account_password(self, self._password) log.change( self.person, 'Account %s: Changed Password' % self) self._password = None save.alters_data = True def can_view(self, request): # if user not authenticated, no access if not request.user.is_authenticated: return False # ensure person making request isn't deleted. if not request.user.is_active: return False # ensure person making request isn't locked. if request.user.is_locked(): return False # if user is admin, full access if is_admin(request): return True # ensure this account is not locked if self.is_locked(): return False # ensure this account is not deleted if self.date_deleted is not None: return False # ensure person owning account isn't locked. if self.person.is_locked(): return False # ensure person owning account isn't deleted. if not self.person.is_active: return False return True def can_edit(self, request): # if we can't view this account, we can't edit it either if not self.can_view(request): return False if not is_admin(request): # if not admin, ensure we are the person being altered if self.person != request.user: return False return True def delete(self, **kwargs): # delete the object log.delete(self.person, 'Account %s: Deleted' % self) super(Account, self).delete(**kwargs) if self.date_deleted is None: # delete the datastore from karaage.datastores import delete_account delete_account(self) delete.alters_data = True def deactivate(self): if self.date_deleted is not None: raise RuntimeError("Account is deactivated") # save the object self.date_deleted = datetime.datetime.now() self.login_enabled = False self.save() # self.save() will delete the datastore for us. deactivate.alters_data = True def change_shell(self, shell): self.shell = shell self.save() # self.save() will update the datastore for us. change_shell.alters_data = True def set_password(self, password): if self.date_deleted is not None: raise RuntimeError("Account is deactivated") self._password = password set_password.alters_data = True def get_disk_quota(self): # FIXME: should this become deprecated? return self.disk_quota def login_shell(self): return self.shell def lock(self): if self.date_deleted is not None: raise RuntimeError("Account is deactivated") self.login_enabled = False self.save() lock.alters_data = True def unlock(self): if self.date_deleted is not None: raise RuntimeError("Account is deactivated") self.login_enabled = True self.save() unlock.alters_data = True def is_locked(self): return not self.login_enabled
class VirtualMachine(VirtualMachineMixin, core_models.RuntimeStateMixin, structure_models.BaseResource): class RuntimeStates: POWERED_OFF = 'POWERED_OFF' POWERED_ON = 'POWERED_ON' SUSPENDED = 'SUSPENDED' CHOICES = ( (POWERED_OFF, 'Powered off'), (POWERED_ON, 'Powered on'), (SUSPENDED, 'Suspended'), ) class GuestPowerStates: RUNNING = 'RUNNING' SHUTTING_DOWN = 'SHUTTING_DOWN' RESETTING = 'RESETTING' STANDBY = 'STANDBY' NOT_RUNNING = 'NOT_RUNNING' UNAVAILABLE = 'UNAVAILABLE' CHOICES = ( (RUNNING, 'Running'), (SHUTTING_DOWN, 'Shutting down'), (RESETTING, 'Resetting'), (STANDBY, 'Standby'), (NOT_RUNNING, 'Not running'), (UNAVAILABLE, 'Unavailable'), ) class ToolsStates: STARTING = 'STARTING' RUNNING = 'RUNNING' NOT_RUNNING = 'NOT_RUNNING' CHOICES = ( (STARTING, 'Starting'), (RUNNING, 'Running'), (NOT_RUNNING, 'Not running'), ) template = models.ForeignKey('Template', null=True, on_delete=models.SET_NULL) cluster = models.ForeignKey('Cluster', null=True, on_delete=models.SET_NULL) datastore = models.ForeignKey('Datastore', null=True, on_delete=models.SET_NULL) folder = models.ForeignKey('Folder', null=True, on_delete=models.SET_NULL) networks = models.ManyToManyField('Network', blank=True) guest_power_enabled = models.BooleanField( default=False, help_text= 'Flag indicating if the virtual machine is ready to process soft power operations.', ) guest_power_state = models.CharField( 'The power state of the guest operating system.', max_length=150, blank=True, choices=GuestPowerStates.CHOICES, ) tools_installed = models.BooleanField(default=False) tools_state = models.CharField( 'Current running status of VMware Tools running in the guest operating system.', max_length=50, blank=True, choices=ToolsStates.CHOICES, ) tracker = FieldTracker() @classmethod def get_backend_fields(cls): return super(VirtualMachine, cls).get_backend_fields() + ( 'runtime_state', 'cores', 'cores_per_socket', 'ram', 'disk', 'tools_installed', 'tools_state', ) @classmethod def get_url_name(cls): return 'vmware-virtual-machine' @property def total_disk(self): return self.disks.aggregate(models.Sum('size'))['size__sum'] or 0 def __str__(self): return self.name
class MysqlUser(models.Model): account = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, null=False, on_delete=models.CASCADE, related_name='mysql_user_account') mysql_database = models.ForeignKey( 'MysqlDatabase', blank=False, null=False, on_delete=models.CASCADE, related_name='mysql_user_mysql_database') product_profile = models.ForeignKey( 'ProductProfile', blank=False, null=False, on_delete=models.CASCADE, related_name='mysql_user_product_profile') date_from = models.DateTimeField(auto_now_add=True) in_queue = models.BooleanField(default=False) is_active = models.BooleanField(default=False) password = models.TextField(blank=False, null=False) permission_alter = models.BooleanField(default=False) permission_create = models.BooleanField(default=False) permission_create_view = models.BooleanField(default=False) permission_delete = models.BooleanField(default=False) permission_drop = models.BooleanField(default=False) permission_index = models.BooleanField(default=False) permission_insert = models.BooleanField(default=False, ) permission_select = models.BooleanField(default=False) permission_show_view = models.BooleanField(default=False) permission_update = models.BooleanField(default=False) username = models.CharField( blank=False, max_length=32, null=False, validators=[ validators.MinLengthValidator(2), validators.RegexValidator('^[a-z][a-z0-9_]+$') ]) tracker = FieldTracker() class Meta: db_table = 'mysql_user' default_permissions = () verbose_name = 'MySQL User' verbose_name_plural = 'MySQL Users'
class Application(models.Model): """ Generic application for anything. """ WAITING_FOR_ADMIN = 'K' COMPLETED = 'C' ARCHIVED = 'A' DECLINED = 'R' secret_token = models.CharField(max_length=64, default=new_random_token, editable=False, unique=True) expires = models.DateTimeField(editable=False) created_by = models.ForeignKey(Person, editable=False, null=True, blank=True) created_date = models.DateTimeField(auto_now_add=True, editable=False) submitted_date = models.DateTimeField(null=True, blank=True) state = models.CharField(max_length=5) complete_date = models.DateTimeField(null=True, blank=True, editable=False) content_type = models.ForeignKey( ContentType, limit_choices_to={'model__in': ['person', 'applicant']}, null=True, blank=True) object_id = models.PositiveIntegerField(null=True, blank=True) applicant = GenericForeignKey() header_message = models.TextField( 'Message', null=True, blank=True, help_text=six.u("Message displayed at top of application form " "for the invitee and also in invitation email")) _class = models.CharField(max_length=100, editable=False) objects = ApplicationManager() _tracker = FieldTracker() class Meta: db_table = "applications_application" def __str__(self): return "Application #%s" % self.id def info(self): return six.text_type(self) def get_type(self): return self._class @models.permalink def get_absolute_url(self): return 'kg_application_detail', [self.id] def save(self, *args, **kwargs): created = self.pk is None if not self.expires: self.expires = datetime.datetime.now() + datetime.timedelta(days=7) if not self.pk: self.created_by = get_current_person() # Find the accessor from Application to type(self) class if hasattr(Application._meta, 'get_fields'): # Django >= 1.8 fields = [ f for f in Application._meta.get_fields() if isinstance(f, OneToOneRel) and f.field.primary_key and f.auto_created ] else: # Django <= 1.8 fields = [ f for f in Application._meta.get_all_related_objects() if isinstance(f, OneToOneRel) and f.field.primary_key ] for rel in fields: # Works with Django < 1.8 and => 1.8 related_model = getattr(rel, 'related_model', rel.model) # if we find it, save the name if related_model == type(self): self._class = rel.get_accessor_name() break super(Application, self).save(*args, **kwargs) if created: log.add(self, 'Created') for field in self._tracker.changed(): log.change(self, 'Changed %s to %s' % (field, getattr(self, field))) save.alters_data = True def delete(self, *args, **kwargs): log.delete(self, 'Deleted') super(Application, self).delete(*args, **kwargs) def get_object(self): if self._class: return getattr(self, self._class) return self def reopen(self): self.submitted_date = None self.complete_date = None self.expires = datetime.datetime.now() + datetime.timedelta(days=7) self.save() def extend(self): self.expires = datetime.datetime.now() + datetime.timedelta(days=7) self.save() def submit(self): self.submitted_date = datetime.datetime.now() self.save() def approve(self, approved_by): assert self.applicant is not None if self.content_type.model == 'applicant': person = self.applicant.approve(approved_by) created_person = True elif self.content_type.model == 'person': person = self.applicant created_person = False else: assert False self.applicant = person self.complete_date = datetime.datetime.now() self.save() return created_person, False approve.alters_data = True def decline(self): self.complete_date = datetime.datetime.now() self.save() decline.alters_data = True def check_valid(self): errors = [] if self.applicant is None: errors.append("Applicant not set.") elif self.content_type.model == 'applicant': errors.extend(self.applicant.check_valid()) return errors def get_roles_for_person(self, person): roles = set() if person == self.applicant: roles.add('is_applicant') roles.add('is_authorised') return roles
class Cart(TimeStampMixin): class Status(models.IntegerChoices): CURRENT = 1 ABANDONED = -1 IN_PROGRESS = 2 CANCELLED = -2 FULFILLED = 3 status_help_text = """ <br/> A Cart can be in 1 of 5 states:<br/> 🛒 <strong>Current</strong> - The currently open cart (each user can only have 1 cart in this state)<br/> ⏳ <strong>In progress</strong> - Cart has been submitted but not yet fulfilled or cancelled<br/> ✅ <strong>Fulfilled</strong> - Items have been delivered to client and payment has been received<br/> ❌ <strong>Cancelled</strong> - Cart can no longer be fulfilled<br/> 🚧 <strong>Abandoned</strong> - The last item in the cart was removed (this will occur automatically)<br/> """ discount_percent_applied_help_text = """ Defaults to using the User's discount percent<br/> """ tax_percent_help_text = """ Defaults to using the User's tax percent<br/> """ closed_at_help_text = """ Date when the cart was last marked as <strong>Fulfilled</strong>, <strong>Cancelled</strong>, or <strong>Abandoned</strong> """ user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE) # 1:M, a user can have many carts status = models.IntegerField(choices=Status.choices, help_text=status_help_text) discount_percent_applied = models.DecimalField( max_digits=5, decimal_places=2, blank=True, validators=[ MinValueValidator(0), MaxValueValidator(100), ], verbose_name='Discount (%)', help_text=discount_percent_applied_help_text) tax_percent_applied = models.DecimalField(max_digits=5, decimal_places=2, blank=True, validators=[ MinValueValidator(0), MaxValueValidator(100), ], verbose_name='Tax (%)', help_text=tax_percent_help_text) ordered_at = models.DateTimeField(null=True, blank=True, verbose_name='Date Ordered') closed_at = models.DateTimeField(null=True, blank=True, verbose_name='Date Closed', help_text=closed_at_help_text) def __str__(self): return f'Cart #{self.id}' # @property # def get_total(self): # return self.cartDetail_set.all().count() # discount_percent_applied and tax_percent_applied default values are pulled from the User when one is not explicitly entered def save(self, *args, **kwargs): if not self.discount_percent_applied: self.discount_percent_applied = self.user.discount_percent if not self.tax_percent_applied: self.tax_percent_applied = self.user.tax_percent super(Cart, self).save(*args, **kwargs) # @receiver(pre_save, sender=Cart) # def get_user_discount_ratio(sender, instance, *args, **kwargs): # instance.discount_ratio = instance.user.discount_ratio def get_subtotal(self): cart = Cart.objects.get(pk=self.pk) subtotal = Decimal( '0.00') # Need to use Decimal type so that 0 is displayed as 0.00 for cartDetail in self.cartdetail_set.all(): subtotal += cartDetail.quantity * cartDetail.tire.price return subtotal get_subtotal.short_description = 'Subtotal ($)' def get_discount_amount(self): return round(self.get_subtotal() * self.discount_percent_applied / 100, 2) get_discount_amount.short_description = 'Discount amount ($)' def get_tax_amount(self): return round(self.get_subtotal() * self.tax_percent_applied / 100, 2) get_tax_amount.short_description = 'Tax amount ($)' def get_total(self): return self.get_subtotal() - self.get_discount_amount( ) + self.get_tax_amount() get_total.short_description = 'Total ($)' def get_owner(self): return self.user.full_name get_owner.short_description = 'Full name' # def get_item_count(self): # cart = Cart.objects.get(id=self.id) # count = 0 # for cartDetail in cart.cartdetail_set.all(): # count += cartDetail.quantity # return count # get_item_count.short_description = 'Number of items' def get_item_count(self): return self.cartdetail_set.all().count() get_item_count.short_description = 'Number of items' status_tracker = FieldTracker(fields=['status']) class Meta: constraints = [ models.UniqueConstraint(fields=['user', 'status'], condition=Q(status=1), name='unique_current_cart') ] @staticmethod def does_state_require_shipping_info(status): if (status == Cart.Status.IN_PROGRESS or status == Cart.Status.FULFILLED or status == Cart.Status.CANCELLED): return True def get_order_number(self): order_number = self.ordershipping.pk return order_number get_order_number.short_description = 'Order #'
class Resource( ResourceDetailsMixin, core_models.UuidMixin, core_models.BackendMixin, TimeStampedModel, core_mixins.ScopeMixin, structure_models.StructureLoggableMixin, ): """ Core resource is abstract model, marketplace resource is not abstract, therefore we don't need to compromise database query efficiency when we are getting a list of all resources. While migration from ad-hoc resources to marketplace as single entry point is pending, the core resource model may continue to be used in plugins and referenced via generic foreign key, and marketplace resource is going to be used as consolidated model for synchronization with external plugins. Eventually it is expected that core resource model is going to be superseded by marketplace resource model as a primary mean. """ class States: CREATING = 1 OK = 2 ERRED = 3 UPDATING = 4 TERMINATING = 5 TERMINATED = 6 CHOICES = ( (CREATING, 'Creating'), (OK, 'OK'), (ERRED, 'Erred'), (UPDATING, 'Updating'), (TERMINATING, 'Terminating'), (TERMINATED, 'Terminated'), ) class Permissions: customer_path = 'project__customer' project_path = 'project' state = FSMIntegerField(default=States.CREATING, choices=States.CHOICES) project = models.ForeignKey(structure_models.Project, on_delete=models.CASCADE) backend_metadata = BetterJSONField(blank=True, default=dict) report = BetterJSONField(blank=True, null=True) current_usages = BetterJSONField(blank=True, default=dict) tracker = FieldTracker() objects = managers.MixinManager('scope') @property def customer(self): return self.project.customer @transition( field=state, source=[States.ERRED, States.CREATING, States.UPDATING], target=States.OK, ) def set_state_ok(self): pass @transition(field=state, source='*', target=States.ERRED) def set_state_erred(self): pass @transition(field=state, source='*', target=States.UPDATING) def set_state_updating(self): pass @transition(field=state, source='*', target=States.TERMINATING) def set_state_terminating(self): pass @transition(field=state, source='*', target=States.TERMINATED) def set_state_terminated(self): pass @property def backend_uuid(self): if self.scope: return self.scope.uuid @property def backend_type(self): if self.scope: scope_type = self.scope.get_scope_type() return scope_type if scope_type else 'Marketplace.Resource' def init_quotas(self): if self.limits: components_map = self.offering.get_limit_components() for key, value in self.limits.items(): component = components_map.get(key) if component: ComponentQuota.objects.create(resource=self, component=component, limit=value) def get_log_fields(self): return ( 'uuid', 'name', 'project', 'offering', 'created', 'modified', 'attributes', 'cost', 'plan', 'limits', 'get_state_display', 'backend_metadata', 'backend_uuid', 'backend_type', ) @property def invoice_registrator_key(self): return self.offering.type @classmethod def get_scope_type(cls): return 'Marketplace.Resource' @classmethod def get_url_name(cls): return 'marketplace-resource' @property def is_expired(self): return self.end_date and self.end_date <= timezone.datetime.today( ).date() def __str__(self): if self.name: return f'{self.name} ({self.offering.name})' if self.scope: return f'{self.name} ({self.content_type} / {self.object_id})' return f'{self.uuid} ({self.offering.name})'
class Language(models.Model): DIRECTION_CHOICES = (("l", "ltr"), ("r", "rtl")) code = models.CharField(max_length=100, unique=True) name = models.CharField(max_length=100, blank=True) anglicized_name = models.CharField(max_length=100, blank=True) country = models.ForeignKey(Country, null=True, blank=True) gateway_language = models.ForeignKey("self", related_name="gateway_to", null=True, blank=True) native_speakers = models.IntegerField(null=True, blank=True) networks_translating = models.ManyToManyField( Network, blank=True, db_table='uw_language_networks_translating') gateway_flag = models.BooleanField(default=False, blank=True, db_index=True) direction = models.CharField(max_length=1, choices=DIRECTION_CHOICES, default="l") iso_639_3 = models.CharField(max_length=3, default="", db_index=True, blank=True, verbose_name="ISO-639-3") extra_data = JSONField(blank=True) tracker = FieldTracker() class Meta: db_table = 'uw_language' def __str__(self): return self.name @property def cc(self): if self.country: return self.country.code.encode("utf-8") return "" @property def cc_all(self): pks = [ int(pk) for pk in self.attributes.filter( attribute="country_id").values_list("value", flat=True) ] countries = Country.objects.filter(pk__in=pks) return [c.code.encode("utf-8") for c in countries] @property def lr(self): if self.country and self.country.region: return self.country.region.name.encode("utf-8") return "" @property def lc(self): return self.code @property def ln(self): return self.name.encode("utf-8") @property def ang(self): return self.anglicized_name @classmethod def codes_text(cls): return " ".join([x.code for x in cls.objects.all().order_by("code")]) @classmethod def names_text(cls): return "\n".join([ "{}\t{}".format(x.code, x.name.encode("utf-8")) for x in cls.objects.all().order_by("code") ]) @classmethod def names_data(cls): return [ dict(pk=x.pk, lc=x.lc, ln=x.ln, ang=x.ang, cc=x.cc_all, lr=x.lr, gw=x.gateway_flag, ld=x.get_direction_display()) for x in cls.objects.all().order_by("code") ]