class Configuration(models.Model): MONITOR_MODE = 1 LOCKDOWN_MODE = 2 CLIENT_MODE_CHOICES = ( (MONITOR_MODE, "Monitor"), (LOCKDOWN_MODE, "Lockdown"), ) PREFLIGHT_MONITOR_MODE = "MONITOR" PREFLIGHT_LOCKDOWN_MODE = "LOCKDOWN" DEFAULT_BATCH_SIZE = 50 LOCAL_CONFIGURATION_ATTRIBUTES = { 'client_mode', 'file_changes_regex', 'file_changes_prefix_filters', 'allowed_path_regex', 'blocked_path_regex', 'enable_page_zero_protection', 'enable_bad_signature_protection', 'more_info_url', 'event_detail_url', 'event_detail_text', 'unknown_block_message', 'banned_block_message', 'mode_notification_monitor', 'mode_notification_lockdown', 'machine_owner_plist', 'machine_owner_key', 'client_auth_certificate_issuer_cn', } SYNC_SERVER_CONFIGURATION_ATTRIBUTES = { # 'client_mode', has to be translated to a string value 'batch_size', 'allowed_path_regex', 'blocked_path_regex', 'enable_bundles', 'enable_transitive_rules' } DEPRECATED_ATTRIBUTES_MAPPING_1_14 = { 'allowed_path_regex': 'whitelist_regex', 'blocked_path_regex': 'blacklist_regex', 'enable_transitive_rules': 'enabled_transitive_whitelisting', } name = models.CharField(max_length=256, unique=True) client_mode = models.IntegerField(choices=CLIENT_MODE_CHOICES, default=MONITOR_MODE) file_changes_regex = models.TextField( blank=True, help_text= "The regex of paths to log file changes. Regexes are specified in ICU format." ) file_changes_prefix_filters = models.TextField( blank=True, help_text= ("A list of ignore prefixes which are checked in-kernel. " "This is more performant than FileChangesRegex when ignoring whole directory trees." )) allowed_path_regex = models.TextField( blank=True, help_text="Matching binaries will be allowed to run, in both modes." "Events will be logged with the 'ALLOW_SCOPE' decision.") blocked_path_regex = models.TextField( blank=True, help_text= "In Monitor mode, executables whose paths are matched by this regex will be blocked." ) enable_page_zero_protection = models.BooleanField( default=True, help_text= "If this flag is set to YES, 32-bit binaries that are missing the __PAGEZERO segment will be blocked" " even in MONITOR mode, unless the binary is whitelisted by an explicit rule." ) enable_bad_signature_protection = models.BooleanField( default=False, help_text= "When enabled, a binary that is signed but has a bad signature (cert revoked, binary tampered with, " "etc.) will be blocked regardless of client-mode unless a binary whitelist." ) more_info_url = models.URLField( blank=True, help_text= 'The URL to open when the user clicks "More Info..." when opening Santa.app. ' 'If unset, the button will not be displayed.') event_detail_url = models.URLField( blank=True, help_text= "When the user gets a block notification, a button can be displayed which will take them " "to a web page with more information about that event." "This property contains a kind of format string to be turned into the URL to send them to. " "The following sequences will be replaced in the final URL: " "%file_sha%, " "%machine_id%, " "%username%, " "%bundle_id%, " "%bundle_ver%.") event_detail_text = models.TextField( blank=True, help_text= "Related to the above property, this string represents the text to show on the button." ) unknown_block_message = models.TextField( default= "The following application has been blocked from executing<br/>\n" "because its trustworthiness cannot be determined.", help_text= "In Lockdown mode this is the message shown to the user when an unknown binary is blocked." ) banned_block_message = models.TextField( default= "The following application has been blocked from executing<br/>\n" "because it has been deemed malicious.", help_text= "This is the message shown to the user when a binary is blocked because of a rule " "if that rule doesn't provide a custom message.") mode_notification_monitor = models.TextField( default="Switching into Monitor mode", help_text= "The notification text to display when the client goes into Monitor mode." ) mode_notification_lockdown = models.TextField( default="Switching into Lockdown mode", help_text= "The notification text to display when the client goes into Lockdown mode." ) machine_owner_plist = models.CharField( blank=True, max_length=512, help_text= "The path to a plist that contains the machine owner key / value pair." ) machine_owner_key = models.CharField( blank=True, max_length=128, help_text="The key to use on the machine owner plist.") # TLS # for the client cert authentication client_certificate_auth = models.BooleanField( "Client certificate authentication", default=False, help_text= "If set, a client certificate will be required for sync authentication. " "Santa will automatically look for a matching certificate " "and its private key in the System keychain, " "if the TLS server advertises the accepted CA certificates. " "If the CA certificates are not sent to the client, " "use the Client Auth Certificate Issuer CN setting.") client_auth_certificate_issuer_cn = models.CharField( "Client auth certificate issuer CN", blank=True, max_length=255, help_text= "If set, this is the Issuer Name of a certificate in the System keychain " "to be used for sync authentication. " "The corresponding private key must also be in the keychain.") # the extra ones only provided via server sync # https://github.com/google/santa/blob/master/Docs/deployment/configuration.md#sync-server-provided-configuration batch_size = models.IntegerField( default=DEFAULT_BATCH_SIZE, validators=[MinValueValidator(5), MaxValueValidator(100)], help_text= "The number of rules to download or events to upload per request. " "Multiple requests will be made if there is more work than can fit in single request." ) enable_bundles = models.BooleanField( default=False, help_text="If set, the bundle scanning feature is enabled.") enable_transitive_rules = models.BooleanField( default=False, help_text="If set, the transitive rule feature is enabled.") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.name def get_absolute_url(self): return reverse("santa:configuration", args=(self.pk, )) def is_monitor_mode(self): return self.client_mode == self.MONITOR_MODE def get_sync_server_config(self, santa_version): config = { k: getattr(self, k) for k in self.SYNC_SERVER_CONFIGURATION_ATTRIBUTES } # translate client mode if self.client_mode == self.MONITOR_MODE: config["client_mode"] = self.PREFLIGHT_MONITOR_MODE elif self.client_mode == self.LOCKDOWN_MODE: config["client_mode"] = self.PREFLIGHT_LOCKDOWN_MODE else: raise NotImplementedError("Unknown santa client mode: {}".format( self.client_mode)) # provide non matching regexp if the regexp are empty for attr in ("allowed_path_regex", "blocked_path_regex"): if not config.get(attr): config[attr] = "NON_MATCHING_PLACEHOLDER_{}".format( get_random_string(8)) # translate attributes for older santa agents santa_version = tuple(int(i) for i in santa_version.split(".")) if santa_version < (1, 14): for attr, deprecated_attr in self.DEPRECATED_ATTRIBUTES_MAPPING_1_14.items( ): config[deprecated_attr] = config.pop(attr) return config def get_local_config(self, min_supported_santa_version=(1, 13)): config = {} for k in self.LOCAL_CONFIGURATION_ATTRIBUTES: v = getattr(self, k) if not v: continue if min_supported_santa_version < ( 1, 14) and k in self.DEPRECATED_ATTRIBUTES_MAPPING_1_14: k = self.DEPRECATED_ATTRIBUTES_MAPPING_1_14[k] config_attr_items = [] for i in k.split("_"): if i == "url": i = "URL" elif i == "cn": i = "CN" else: i = i.capitalize() config_attr_items.append(i) config_attr = "".join(config_attr_items) config[config_attr] = v return config def save(self, *args, **kwargs): super().save(*args, **kwargs) for enrollment in self.enrollment_set.all(): # per default, will bump the enrollment version # and notify their distributors enrollment.save()
class BeerReview(models.Model): user=models.ForeignKey(User,on_delete=models.CASCADE) beer_pk=models.IntegerField() beer_name=models.CharField(max_length=100) review=models.TextField() score=models.DecimalField(max_digits=5,decimal_places=2,validators=[MinValueValidator(0.0),MaxValueValidator(5.0)]) published_date=models.DateTimeField(blank=True, null=True) def publish(self): self.published_date=timezone.now() self.save()
class BaseProfile(Timestamped): user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) company = models.ForeignKey(Company, blank=True, null=True, on_delete=models.CASCADE) proximity_id = models.CharField(max_length=20, blank=True, null=True) joining_date = models.DateField(blank=True, null=True) designation = models.ForeignKey(Designation, blank=True, null=True, on_delete=models.CASCADE) # photo = models.ImageField(upload_to=os.path.join(settings.BASE_DIR, 'media', 'user', 'photo'), blank=True, null=True) photo = models.ImageField(upload_to='user/photo/', blank=True, null=True) MALE = 1 FEMALE = 2 THIRD_GENDER = 3 GENDER_CHOICES = [ (MALE, 'Male'), (FEMALE, 'Female'), (THIRD_GENDER, 'Third Gender'), ] gender = models.SmallIntegerField(validators=[MaxValueValidator(3)], choices=GENDER_CHOICES, default=MALE) B_P = 1 B_N = 2 A_P = 3 A_N = 4 AB_P = 5 AB_N = 6 O_P = 7 O_N = 8 BLOOD_GROUP_CHOICES = ( (B_P, 'B +ve'), (B_N, 'B -ve'), (A_P, 'A +ve'), (A_N, 'A -ve'), (AB_P, 'AB +ve'), (AB_N, 'AB -ve'), (O_P, 'O +ve'), (O_N, 'O -ve'), ) blood_group = models.SmallIntegerField(choices=BLOOD_GROUP_CHOICES, default=B_P) ISLAM = 1 HINDUISM = 2 BUDDHISM = 3 CHRISTIANITY = 4 RELIGION_CHOICES = ( (ISLAM, 'Islam'), (HINDUISM, 'Hinduism'), (BUDDHISM, 'Buddhism'), (CHRISTIANITY, 'Christianity'), ) religion = models.SmallIntegerField(choices=RELIGION_CHOICES, default=ISLAM) nationality = models.CharField(max_length=20, default='Bangladeshi') married = models.BooleanField(default=False) children = models.SmallIntegerField(default=0, blank=True, null=True) address_line_1 = models.CharField(max_length=100, blank=True, null=True) address_line_2 = models.CharField(max_length=100, blank=True, null=True) phone_1 = models.CharField(max_length=50, blank=True, null=True) phone_2 = models.CharField(max_length=50, blank=True, null=True) date_of_birth = models.DateField(blank=True, null=True) national_id = models.CharField(max_length=20, blank=True, null=True) national_id_new = models.CharField(max_length=20, blank=True, null=True) nominee = models.CharField(max_length=50, blank=True, null=True) nominee_address_line_1 = models.CharField(max_length=100, blank=True, null=True) nominee_address_line_2 = models.CharField(max_length=100, blank=True, null=True) nominee_phone_1 = models.CharField(max_length=50, blank=True, null=True) nominee_phone_2 = models.CharField(max_length=50, blank=True, null=True) nominee_national_id = models.CharField(max_length=20, blank=True, null=True) nominee_national_id_new = models.CharField(max_length=20, blank=True, null=True) spouse = models.CharField(max_length=50, blank=True, null=True) spouses_contact = models.CharField(max_length=50, blank=True, null=True) fathers_name = models.CharField(max_length=50, blank=True, null=True) fathers_contact = models.CharField(max_length=50, blank=True, null=True) mothers_name = models.CharField(max_length=50, blank=True, null=True) mothers_contact = models.CharField(max_length=50, blank=True, null=True) department = models.ForeignKey(Department, blank=True, null=True, on_delete=models.CASCADE) section = models.ForeignKey(Section, blank=True, null=True, on_delete=models.CASCADE) building = models.ForeignKey(Building, blank=True, null=True, on_delete=models.CASCADE) floor = models.ForeignKey(Floor, blank=True, null=True, on_delete=models.CASCADE) line = models.ForeignKey(Line, blank=True, null=True, on_delete=models.CASCADE) shift = models.ForeignKey(Shift, blank=True, null=True, on_delete=models.CASCADE) class Meta: abstract = True
class Transaction(models.Model): ''' Saves each transaction's details for internal record Used to create records of TransactionBSE and TransactionXsipBSE that are sent to BSEStar's API endpoints ''' # status of the transaction. most imp states are 1, 2 and 6 for bse STATUS = ( ('0', 'Requested internally'), # bse order not placed yet ('1', 'Cancelled/Failed- refer to status_comment for reason'), ('2', 'Order successfully placed at BSE'), ('4', 'Redirected after payment'), ('5', 'Payment provisionally made'), ('6', 'Order sucessfully completed at BSE'), ('7', 'Reversed'), # when investment has been redeemed ('8', 'Concluded'), # valid for SIP only when SIP completed/stopped ) TRANSACTIONTYPE = ( ('P', 'Purchase'), ('R', 'Redemption'), ('A', 'Additional Purchase'), ) ORDERTYPE = ( ('1', 'Lumpsum'), ('2', 'SIP'), ) user = models.ForeignKey(Info,\ on_delete=models.PROTECT,\ related_name='transactions',\ related_query_name='transaction') scheme_plan = models.ForeignKey(SchemePlan,\ on_delete=models.PROTECT,\ related_name='transactions',\ related_query_name='transaction') transaction_type = models.CharField(max_length=1, blank=False, choices=TRANSACTIONTYPE, default='P') ##purchase redemption etc order_type = models.CharField(max_length=1, blank=False, choices=ORDERTYPE, default='1') ##lumpsum or sip # track status of transaction and comments if any from bse or rta status = models.CharField(max_length=1, choices=STATUS, default='0') status_comment = models.CharField(max_length=1000, blank=True) amount = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(1000000)], blank=True, null=True) # for redeem transactions all_redeem = models.NullBooleanField(blank=True, null=True) ## Null means not redeem transaction, True means redeem all, False means redeem 'amount' # for SIP transactions sip_num_inst = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(120)], blank=True, null=True) sip_start_date = models.DateField(blank=True, null=True) ## update this field after every instalment of sip sip_num_inst_done = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(120)], blank=True, null=True, default=0) ## add datetime_at_mf of each instalment sip_dates = models.CharField(max_length=255, blank=True) ## add bse order_id of each instalment sip_order_ids = models.CharField(max_length=255, blank=True) mandate = models.ForeignKey(Mandate,\ on_delete=models.PROTECT,\ null=True,\ related_name='transactions',\ related_query_name='transaction') # datetimes of importance ## datetime when order was placed on bsestar datetime_at_mf = models.DateTimeField(auto_now=False, auto_now_add=False, blank=True, null=True) #datetime of purchase of units on mf created = models.DateTimeField(auto_now_add=True) # set these fields after the transaction is successfully PLACED ## this is the trans_no of the order on bsestar ## that has successfully placed this transaction bse_trans_no = models.CharField(max_length=20, blank=True) # set these fields after the transaction is successfully COMPLETED folio_number = models.CharField(max_length=25, blank=True) # Returns - set these fields daily after transaction is COMPLETED return_till_date = models.FloatField(blank=True, null=True) #annualised compounded annually. make it absolute return return_date = models.DateField(auto_now=False, auto_now_add=False, blank=True, null=True) #date as of return calculated return_grade = models.CharField(max_length=200, blank=True)
class Rating(models.Model): Insight = models.ForeignKey(Insight) Author = models.ForeignKey(User) Text = models.CharField(max_length=100) Rating = models.IntegerField(validators=(MaxValueValidator(5), MinValueValidator(1)))
from django.core.validators import MinValueValidator, MaxValueValidator year_validators = [MinValueValidator(1930), MaxValueValidator(2100)] mark5_validators = [MinValueValidator(2), MaxValueValidator(5)] level_validators = [MinValueValidator(1), MaxValueValidator(7)]
class Order(models.Model): STATUS = ( ('Completed', 'Completed'), ('In Progress', 'In Progress'), ('Cancelled', 'Cancelled') ) car = models.ForeignKey(Car, null=True, on_delete=models.SET_NULL) date = models.DateTimeField(auto_now_add=True, null=True) customer = models.ForeignKey(Customer, null=True, on_delete=models.SET_NULL) Order_amount = models.FloatField(max_length=64, null=True,validators=[MinValueValidator(1), MaxValueValidator(10000)]) Order_status = models.CharField(max_length=64, null=True, choices=STATUS) def __str__(self): return self.car.Make + " " + self.car.Model
class Article(Publishable, AuthorMixin): parent = ForeignKey('Article', related_name='article_parent', blank=True, null=True) headline = CharField(max_length=255) section = ForeignKey('Section') subsection = ForeignKey('Subsection', related_name='article_subsection', blank=True, null=True) authors = ManyToManyField('Author', related_name='article_authors') topic = ForeignKey('Topic', null=True) tags = ManyToManyField('Tag') is_breaking = BooleanField(default=False) breaking_timeout = DateTimeField(blank=True, null=True) IMPORTANCE_CHOICES = [(i, i) for i in range(1, 6)] importance = PositiveIntegerField(validators=[MaxValueValidator(5)], choices=IMPORTANCE_CHOICES, default=3) READING_CHOICES = ( ('anytime', 'Anytime'), ('morning', 'Morning'), ('midday', 'Midday'), ('evening', 'Evening'), ) reading_time = CharField(max_length=100, choices=READING_CHOICES, default='anytime') class Meta: unique_together = ( ('slug', 'head'), ('parent', 'slug', 'head'), ('parent', 'slug', 'is_published'), ) AuthorModel = Author @property def title(self): return self.headline def get_related(self): return Article.objects.exclude(pk=self.id).filter( section=self.section, is_published=True).order_by('-published_at')[:5] def get_reading_list(self, ref=None, dur=None): articles = self.get_related() name = self.section.name return { 'ids': ",".join([str(a.parent_id) for a in articles]), 'name': name } def is_currently_breaking(self): if self.is_published and self.is_breaking: if self.breaking_timeout: return timezone.now() < self.breaking_timeout return False def save_tags(self, tag_ids): self.tags.clear() for tag_id in tag_ids: try: tag = Tag.objects.get(id=int(tag_id)) self.tags.add(tag) except Tag.DoesNotExist: pass def save_topic(self, topic_id): if topic_id is None: self.topic = None else: try: topic = Topic.objects.get(id=int(topic_id)) topic.update_timestamp() self.topic = topic except Topic.DoesNotExist: pass def get_absolute_url(self): """ Returns article URL. """ return "%s%s/%s/" % (settings.BASE_URL, self.section.slug, self.slug) def get_subsection(self): """ Returns the subsection set in the parent article """ return self.parent.subsection def save_subsection(self, subsection_id): """ Save the subsection to the parent article """ Article.objects.filter(parent_id=self.parent.id).update( subsection_id=subsection_id)
class AdministrativeRegion(models.Model): """ Helps with downloading / importing openstreetmap regions. Makes it possible for end users to add regions without altering code and then import / update those regions. Caveats: - The more detail you need, the more data is downloaded and processed. This can go into extremes when working with cities. Our advice is to only download larger regions or have a massive setup to convert the data. Your memory might not be adequate in those cases. - Importing regions can be excruciatingly slow, even up to hours and days, depending on the size. - Importing regions will possibly block the worker that is importing the region for said time. """ country = CountryField(db_index=True) organization_type = models.ForeignKey( OrganizationType, on_delete=models.CASCADE, help_text= "The organization type desired to import. Not all organization types might be present in this list" " by default. Create new ones accordingly.", ) admin_level = models.IntegerField( help_text=mark_safe( "The administrative level as documented on the OSM Wiki. Note that each country uses a different way " "to organize the same thing. Some use municipalities on level 8, other on level 4 etc. Really do " "check the wiki before adding any missing organization. " "<a href='https://wiki.openstreetmap.org/wiki/Tag:boundary=administrative' target='_blank'>" "Visit the OSM wiki</a>."), default=8, validators=[MinValueValidator(1), MaxValueValidator(11)], ) resampling_resolution = models.FloatField( help_text= "This is used in the algorithm that reduces datapoints in map shapes: this saves a lot of data. " "value here should make the map look decent when the entire country is visible but may be somewhat " "blocky when zooming in. The smaller the number, the more detail.", default="0.001", ) imported = models.BooleanField( help_text= "When imported, this is checked. Helps with importing a larger number of regions manually.", default=False, ) import_start_date = models.DateTimeField(blank=True, null=True) import_message = models.CharField( max_length=255, default="", blank=True, null=True, help_text="Information returned from the import features.") class Meta: verbose_name = _("administrative_region") verbose_name_plural = _("administrative_regions") def __str__(self): return "%s/%s" % ( self.country, self.organization_type, )
class Customer(models.Model): name = models.CharField(max_length=60) email = models.EmailField() age = models.PositiveIntegerField( validators=[MinValueValidator(16), MaxValueValidator(120)])
class IPIBase(models.Model): """Abstract base for all objects containing IPI numbers. Attributes: generally_controlled (django.db.models.BooleanField): General agreement (renamed in verbose_name) ipi_base (django.db.models.CharField): IPI Base Number ipi_name (django.db.models.CharField): IPI Name Number pr_society (django.db.models.CharField): Performing Rights Society Code publisher_fee (django.db.models.DecimalField): Publisher Fee saan (django.db.models.CharField): Society-assigned agreement number, in this context it is used for general agreements, for specific agreements use :attr:`.models.WriterInWork.saan`. """ class Meta: abstract = True ipi_name = models.CharField( 'IPI Name #', max_length=11, blank=True, null=True, unique=True, validators=(CWRFieldValidator('writer_ipi_name'), )) ipi_base = models.CharField( 'IPI Base #', max_length=15, blank=True, null=True, validators=(CWRFieldValidator('writer_ipi_base'), )) pr_society = models.CharField( 'Performing rights society', max_length=3, blank=True, null=True, validators=(CWRFieldValidator('writer_pr_society'), ), choices=SOCIETIES) saan = models.CharField( 'Society-assigned agreement number', help_text='Use this field for general agreements only.', validators=(CWRFieldValidator('saan'), ), max_length=14, blank=True, null=True, unique=True) _can_be_controlled = models.BooleanField(editable=False, default=False) generally_controlled = models.BooleanField('General agreement', default=False) publisher_fee = models.DecimalField( max_digits=5, decimal_places=2, blank=True, null=True, validators=[MinValueValidator(0), MaxValueValidator(100)], help_text='Percentage of royalties kept by the publisher') def clean_fields(self, *args, **kwargs): if self.saan: self.saan = self.saan.upper() if self.ipi_name: self.ipi_name = self.ipi_name.rjust(11, '0') if self.ipi_base: self.ipi_base = self.ipi_base.replace('.', '') self.ipi_base = re.sub(r'(I).?(\d{9}).?(\d)', r'\1-\2-\3', self.ipi_base) return super().clean_fields(*args, **kwargs) def clean(self, enforce_ipi_name=ENFORCE_IPI_NAME, enforce_pr_society=ENFORCE_PR_SOCIETY, enforce_saan=ENFORCE_SAAN, enforce_publisher_fee=ENFORCE_PUBLISHER_FEE, error_msg=CAN_NOT_BE_CONTROLLED_MSG): """Clean with a lot of arguments. In DMP they come from settings, but in other (closed source) solutions that use this code, these values are set dynamically. Args: enforce_ipi_name (bool, optional): Makes IPI Name # required if controlled enforce_pr_society (bool, optional): Makes PR Society code required if controlled enforce_saan (bool, optional): Makes SAAN required if controlled enforce_publisher_fee (bool, optional): Makes Publisher fee required if controlled error_msg (str, optional): Error Message to show if required fields are not filled out """ self._can_be_controlled = True if enforce_ipi_name: self._can_be_controlled &= bool(self.ipi_name) if enforce_pr_society: self._can_be_controlled &= bool(self.pr_society) d = {} if not self.generally_controlled: if self.saan: d['saan'] = 'Only for a general agreement.' if self.publisher_fee: d['publisher_fee'] = 'Only for a general agreement.' else: if not self._can_be_controlled: d['generally_controlled'] = error_msg if enforce_saan and not self.saan: d['saan'] = 'This field is required.' if enforce_publisher_fee and not self.publisher_fee: d['publisher_fee'] = 'This field is required.' if d: raise ValidationError(d)
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}' # 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) def get_subtotal(self): 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.get_relevant_tire().relevant_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.get_discount_amount()) * 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_full_name(self): return self.user.full_name get_full_name.short_description = 'Full name' 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 Meta: verbose_name = '🛒 Cart & Order' verbose_name_plural = '🛒 Carts & Orders'
class ProductRating(models.Model): product = models.ForeignKey("Product", on_delete=models.CASCADE, related_name="ratings") customer = models.ForeignKey(Customer, on_delete=models.CASCADE) rating = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(5)])
class MultiCourseDiscount(Discount): discount = Discount() num_sessions = models.IntegerField( validators=[MinValueValidator(2), MaxValueValidator(1000)], )
class PlanCost(models.Model): """Cost and frequency of billing for a plan.""" id = models.UUIDField( default=uuid4, editable=False, primary_key=True, verbose_name='ID', ) plan = models.ForeignKey( SubscriptionPlan, help_text=_('the subscription plan for these cost details'), on_delete=models.CASCADE, related_name='costs', ) recurrence_period = models.PositiveSmallIntegerField( default=1, help_text=_('how often the plan is billed (per recurrence unit)'), validators=[MinValueValidator(1)], ) recurrence_unit = models.PositiveIntegerField( default=6, help_text=_('the unit of measurement for the recurrence period'), validators=[MaxValueValidator(7)], ) cost = models.DecimalField( blank=True, decimal_places=4, help_text=_('the cost per recurrence of the plan'), max_digits=19, null=True, ) class Meta: ordering = ('recurrence_unit', 'recurrence_period', 'cost',) @property def display_recurrent_unit_text(self): """Converts recurrence_unit integer to text.""" conversion = [ 'one-time', 'per second', 'per minute', 'per hour', 'per day', 'per week', 'per month', 'per year', ] return conversion[self.recurrence_unit] @property def display_billing_frequency_text(self): """Generates human-readable billing frequency.""" conversion = [ 'one-time', {'singular': 'per second', 'plural': 'seconds'}, {'singular': 'per minute', 'plural': 'minutes'}, {'singular': 'per hour', 'plural': 'hours'}, {'singular': 'per day', 'plural': 'days'}, {'singular': 'per week', 'plural': 'weeks'}, {'singular': 'per month', 'plural': 'months'}, {'singular': 'per year', 'plural': 'years'}, ] if self.recurrence_unit == 0: return conversion[0] if self.recurrence_period == 1: return conversion[self.recurrence_unit]['singular'] return 'every {} {}'.format( self.recurrence_period, conversion[self.recurrence_unit]['plural'] ) def next_billing_datetime(self, current): """Calculates next billing date for provided datetime. Parameters: current (datetime): The current datetime to compare against. Returns: datetime: The next time billing will be due. """ if self.recurrence_unit == 1: return current + timedelta(seconds=self.recurrence_period) if self.recurrence_unit == 2: return current + timedelta(minutes=self.recurrence_period) if self.recurrence_unit == 3: return current + timedelta(hours=self.recurrence_period) if self.recurrence_unit == 4: return current + timedelta(days=self.recurrence_period) if self.recurrence_unit == 5: return current + timedelta(weeks=self.recurrence_period) if self.recurrence_unit == 6: # Adds the average number of days per month as per: # http://en.wikipedia.org/wiki/Month#Julian_and_Gregorian_calendars # This handle any issues with months < 31 days and leap years return current + timedelta( days=30.4368 * self.recurrence_period ) if self.recurrence_unit == 7: # Adds the average number of days per year as per: # http://en.wikipedia.org/wiki/Year#Calendar_year # This handle any issues with leap years return current + timedelta( days=365.2425 * self.recurrence_period ) return None
class Votos(models.Model): usuario = models.ForeignKey(User, null=True, blank=True) lugar = models.ForeignKey(Place, null=True, blank=True) valor = models.IntegerField(verbose_name='Puntuación media inicial', validators=[MaxValueValidator(100), MinValueValidator(0)])
class Daily(models.Model): user = models.ForeignKey(User,related_name='daily',on_delete=CASCADE) goal = models.CharField(max_length=100) date = models.DateField(auto_now_add=True) ## user = models.ForeignKey(User,related_name='daily',on_delete=CASCADE) #The default form widget for this field is a single DateTimeInput. #The admin uses two separate TextInput widgets with JavaScript shortcuts. studytime_h = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(23)]) studytime_m = models.PositiveIntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(59)]) feelings = models.TextField(blank=True) #The default form widget for this field is a DateInput. #The admin adds a JavaScript calendar, and a shortcut for “Today” # d_day = models.DateField() #체크리스트, 할일은 자녀테이블로 알아서 연결돼있을것 def __str__(self): return str(self.user)+str(self.date)
class Myrating(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) movie = models.ForeignKey(Movie, on_delete=models.CASCADE) rating = models.IntegerField(default=1, validators=[MaxValueValidator(5), MinValueValidator(0)])
class DecimalFieldModel(models.Model): decimal_field = models.DecimalField( max_digits=3, decimal_places=1, validators=[MinValueValidator(1), MaxValueValidator(3)])
class Rent(TimeStampedSafeDeleteModel): """ In Finnish: Vuokran perustiedot """ lease = models.ForeignKey('leasing.Lease', verbose_name=_("Lease"), related_name='rents', on_delete=models.PROTECT) # In Finnish: Vuokralaji type = EnumField(RentType, verbose_name=_("Type"), max_length=30) # In Finnish: Vuokrakausi cycle = EnumField(RentCycle, verbose_name=_("Cycle"), null=True, blank=True, max_length=30) # In Finnish: Indeksin tunnusnumero index_type = EnumField(IndexType, verbose_name=_("Index type"), null=True, blank=True, max_length=30) # In Finnish: Laskutusjako due_dates_type = EnumField(DueDatesType, verbose_name=_("Due dates type"), null=True, blank=True, max_length=30) # In Finnish: Laskut kpl / vuodessa due_dates_per_year = models.PositiveIntegerField( verbose_name=_("Due dates per year"), null=True, blank=True) # In Finnish: Perusindeksi elementary_index = models.PositiveIntegerField( verbose_name=_("Elementary index"), null=True, blank=True) # In Finnish: Pyöristys index_rounding = models.PositiveIntegerField( verbose_name=_("Index rounding"), null=True, blank=True) # In Finnish: X-luku x_value = models.PositiveIntegerField(verbose_name=_("X value"), null=True, blank=True) # In Finnish: Y-luku y_value = models.PositiveIntegerField(verbose_name=_("Y value"), null=True, blank=True) # In Finnish: Y-alkaen y_value_start = models.DateField(verbose_name=_("Y value start date"), null=True, blank=True) # In Finnish: Tasaus alkupvm equalization_start_date = models.DateField( verbose_name=_("Equalization start date"), null=True, blank=True) # In Finnish: Tasaus loppupvm equalization_end_date = models.DateField( verbose_name=_("Equalization end date"), null=True, blank=True) # In Finnish: Määrä (vain kun tyyppi on kertakaikkinen vuokra) amount = models.DecimalField(verbose_name=_("Amount"), null=True, blank=True, max_digits=10, decimal_places=2) # In Finnish: Kommentti note = models.TextField(verbose_name=_("Note"), null=True, blank=True) # In Finnish: Alkupvm start_date = models.DateField(verbose_name=_("Start date"), null=True, blank=True) # In Finnish: Loppupvm end_date = models.DateField(verbose_name=_("End date"), null=True, blank=True) # In Finnish: Kausilaskutus seasonal_start_day = models.PositiveIntegerField( verbose_name=_("Seasonal start day"), null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(31)]) seasonal_start_month = models.PositiveIntegerField( verbose_name=_("Seasonal start month"), null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(12)]) seasonal_end_day = models.PositiveIntegerField( verbose_name=_("Seasonal end day"), null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(31)]) seasonal_end_month = models.PositiveIntegerField( verbose_name=_("Seasonal end month"), null=True, blank=True, validators=[MinValueValidator(1), MaxValueValidator(12)]) # These ratios are used if the rent type is MANUAL # manual_ratio is used for the whole year if the RentCycle is # JANUARY_TO_DECEMBER. If the Rent Cycle is APRIL_TO_MARCH, this is used for 1.4. - 31.12. # In Finnish: Kerroin (Käsinlaskenta) manual_ratio = models.DecimalField(verbose_name=_("Manual ratio"), null=True, blank=True, max_digits=10, decimal_places=2) # If the Rent Cycle is APRIL_TO_MARCH, manual_ratio_previous is used for 1.1. - 31.3. # In Finnish: Aiempi kerroin (Käsinlaskenta) manual_ratio_previous = models.DecimalField( verbose_name=_("Manual ratio (previous)"), null=True, blank=True, max_digits=10, decimal_places=2) class Meta: verbose_name = pgettext_lazy("Model name", "Rent") verbose_name_plural = pgettext_lazy("Model name", "Rents") def is_seasonal(self): return (self.seasonal_start_day and self.seasonal_start_month and self.seasonal_end_day and self.seasonal_end_month) def get_amount_for_year(self, year): date_range_start = datetime.date(year, 1, 1) date_range_end = datetime.date(year, 12, 31) return self.get_amount_for_date_range(date_range_start, date_range_end) def get_amount_for_month(self, year, month): date_range_start = datetime.date(year, month, 1) date_range_end = datetime.date(year, month, 1) + relativedelta(day=31) return self.get_amount_for_date_range(date_range_start, date_range_end) def get_amount_for_date_range(self, date_range_start, date_range_end, explain=False): # noqa: C901 TODO assert date_range_start <= date_range_end, 'date_range_start cannot be after date_range_end.' explanation = Explanation() range_filtering = Q( Q(Q(end_date=None) | Q(end_date__gte=date_range_start)) & Q(Q(start_date=None) | Q(start_date__lte=date_range_end))) fixed_initial_year_rents = self.fixed_initial_year_rents.filter( range_filtering) contract_rents = self.contract_rents.filter(range_filtering) rent_adjustments = self.rent_adjustments.filter(range_filtering) total = Decimal('0.00') fixed_applied = False remaining_ranges = [] # TODO: seasonal spanning multiple years if self.is_seasonal(): seasonal_period_start = datetime.date( year=date_range_start.year, month=self.seasonal_start_month, day=self.seasonal_start_day) seasonal_period_end = datetime.date(year=date_range_start.year, month=self.seasonal_end_month, day=self.seasonal_end_day) if date_range_start < seasonal_period_start and date_range_start < seasonal_period_end: date_range_start = seasonal_period_start if date_range_end > seasonal_period_end and date_range_end > seasonal_period_start: date_range_end = seasonal_period_end else: if ((self.start_date and date_range_start < self.start_date) and (not self.end_date or date_range_start < self.end_date)): date_range_start = self.start_date if ((self.end_date and date_range_end > self.end_date) and (self.start_date and date_range_end > self.start_date)): date_range_end = self.end_date for fixed_initial_year_rent in fixed_initial_year_rents: (fixed_overlap, fixed_remainders) = get_range_overlap_and_remainder( date_range_start, date_range_end, *fixed_initial_year_rent.date_range) if not fixed_overlap: continue if fixed_remainders: remaining_ranges.extend(fixed_remainders) fixed_applied = True fixed_amount = fixed_initial_year_rent.get_amount_for_date_range( *fixed_overlap) fixed_explanation_item = explanation.add( subject=fixed_initial_year_rent, date_ranges=[fixed_overlap], amount=fixed_amount) for rent_adjustment in rent_adjustments: if fixed_initial_year_rent.intended_use and \ rent_adjustment.intended_use != fixed_initial_year_rent.intended_use: continue (adjustment_overlap, adjustment_remainders) = get_range_overlap_and_remainder( fixed_overlap[0], fixed_overlap[1], *rent_adjustment.date_range) if not adjustment_overlap: continue tmp_amount = fix_amount_for_overlap(fixed_amount, adjustment_overlap, adjustment_remainders) adjustment_amount = rent_adjustment.get_amount_for_date_range( tmp_amount, *adjustment_overlap) fixed_amount += adjustment_amount explanation.add(subject=rent_adjustment, date_ranges=[adjustment_overlap], amount=adjustment_amount, related_item=fixed_explanation_item) total += fixed_amount if fixed_applied: if not remaining_ranges: if explain: explanation.add(subject=self, date_ranges=[(date_range_start, date_range_end)], amount=total) return total, explanation else: return total else: date_ranges = remaining_ranges else: date_ranges = [(date_range_start, date_range_end)] # We may need to calculate multiple separate ranges if the rent # type is index or manual because the index number could be different # in different years. if self.type in [RentType.INDEX, RentType.MANUAL]: date_ranges = self.split_ranges_by_cycle(date_ranges) for (range_start, range_end) in date_ranges: if self.type == RentType.ONE_TIME: total += self.amount continue for contract_rent in contract_rents: (contract_overlap, _remainder) = get_range_overlap_and_remainder( range_start, range_end, *contract_rent.date_range) if not contract_overlap: continue if self.type == RentType.FIXED: contract_amount = contract_rent.get_amount_for_date_range( *contract_overlap) contract_rent_explanation_item = explanation.add( subject=contract_rent, date_ranges=[contract_overlap], amount=contract_amount) elif self.type == RentType.MANUAL: contract_amount = contract_rent.get_amount_for_date_range( *contract_overlap) explanation.add(subject=contract_rent, date_ranges=[contract_overlap], amount=contract_amount) manual_ratio = self.manual_ratio if self.cycle == RentCycle.APRIL_TO_MARCH and is_date_on_first_quarter( contract_overlap[0]): manual_ratio = self.manual_ratio_previous contract_amount *= manual_ratio contract_rent_explanation_item = explanation.add( subject={ "subject_type": "ratio", "description": _("Manual ratio {ratio}").format( ratio=manual_ratio), }, date_ranges=[contract_overlap], amount=contract_amount) elif self.type == RentType.INDEX: original_rent_amount = contract_rent.get_base_amount_for_date_range( *contract_overlap) index = self.get_index_for_date(contract_overlap[0]) index_calculation = IndexCalculation( amount=original_rent_amount, index=index, index_type=self.index_type, precision=self.index_rounding, x_value=self.x_value, y_value=self.y_value) contract_amount = index_calculation.calculate() contract_rent_explanation_item = explanation.add( subject=contract_rent, date_ranges=[contract_overlap], amount=original_rent_amount) index_explanation_item = explanation.add( subject=index, date_ranges=[contract_overlap], amount=contract_amount, related_item=contract_rent_explanation_item) for item in index_calculation.explanation_items: explanation.add_item( item, related_item=index_explanation_item) elif self.type == RentType.FREE: continue else: raise NotImplementedError( 'RentType {} not implemented'.format(self.type)) for rent_adjustment in rent_adjustments: if rent_adjustment.intended_use != contract_rent.intended_use: continue (adjustment_overlap, adjustment_remainders) = get_range_overlap_and_remainder( contract_overlap[0], contract_overlap[1], *rent_adjustment.date_range) if not adjustment_overlap: continue tmp_amount = fix_amount_for_overlap( contract_amount, adjustment_overlap, adjustment_remainders) adjustment_amount = rent_adjustment.get_amount_for_date_range( tmp_amount, *adjustment_overlap) contract_amount += adjustment_amount explanation.add( subject=rent_adjustment, date_ranges=[adjustment_overlap], amount=adjustment_amount, related_item=contract_rent_explanation_item) total += max(Decimal(0), contract_amount) explanation.add(subject=self, date_ranges=[(date_range_start, date_range_end)], amount=total) if explain: return total, explanation else: return total def get_custom_due_dates_as_daymonths(self): if self.due_dates_type != DueDatesType.CUSTOM: return set() return [ dd.as_daymonth() for dd in self.due_dates.all().order_by('month', 'day') ] def get_due_dates_as_daymonths(self): due_dates = [] if self.due_dates_type == DueDatesType.FIXED: # TODO: handle unknown due date count if self.due_dates_per_year in (1, 2, 4, 12): due_dates = FIXED_DUE_DATES[ self.lease.type.due_dates_position][ self.due_dates_per_year] elif self.due_dates_type == DueDatesType.CUSTOM: due_dates = self.get_custom_due_dates_as_daymonths() return due_dates def get_due_dates_for_period(self, start_date, end_date): if (self.end_date and start_date > self.end_date) or ( self.start_date and end_date < self.start_date): return [] rent_due_dates = self.get_due_dates_as_daymonths() due_dates = [] for rent_due_date in rent_due_dates: for year in range(start_date.year, end_date.year + 1): tmp_date = datetime.date(year=year, month=rent_due_date.month, day=rent_due_date.day) if start_date <= tmp_date <= end_date: due_dates.append(tmp_date) return due_dates def get_billing_period_from_due_date(self, due_date): if not due_date: return None # Non-seasonal rent if not self.is_seasonal(): due_dates_per_year = self.get_due_dates_for_period( datetime.date(year=due_date.year, month=1, day=1), datetime.date(year=due_date.year, month=12, day=31)) try: due_date_index = due_dates_per_year.index(due_date) return get_billing_periods_for_year( due_date.year, len(due_dates_per_year))[due_date_index] except (ValueError, IndexError): # TODO: better error handling return None # Seasonal rent seasonal_period_start = datetime.date(year=due_date.year, month=self.seasonal_start_month, day=self.seasonal_start_day) seasonal_period_end = datetime.date(year=due_date.year, month=self.seasonal_end_month, day=self.seasonal_end_day) if seasonal_period_start > due_date or seasonal_period_end < due_date: return None due_dates_in_period = self.get_due_dates_for_period( seasonal_period_start, seasonal_period_end) if not due_dates_in_period: return None elif len(due_dates_in_period ) == 1 and due_dates_in_period[0] == due_date: return seasonal_period_start, seasonal_period_end else: try: due_date_index = due_dates_in_period.index(due_date) except ValueError: return None return split_date_range( (seasonal_period_start, seasonal_period_end), len(due_dates_in_period))[due_date_index] def split_range_by_cycle(self, date_range_start, date_range_end): if not self.cycle: return [(date_range_start, date_range_end)] ranges = [(date_range_start, date_range_end)] years = range(date_range_start.year, date_range_end.year + 1) for year in years: if self.cycle == RentCycle.APRIL_TO_MARCH: cycle_change_date = datetime.date(year=year, month=4, day=1) else: cycle_change_date = datetime.date(year=year, month=1, day=1) for i, (range_start_date, range_end_date) in enumerate(ranges): if range_start_date < cycle_change_date < range_end_date: del ranges[i] ranges.extend([(range_start_date, cycle_change_date - relativedelta(days=1)), (cycle_change_date, range_end_date)]) return ranges def split_ranges_by_cycle(self, ranges): if not self.cycle or self.cycle != RentCycle.APRIL_TO_MARCH: return ranges new_ranges = [] for one_range in ranges: new_ranges.extend(self.split_range_by_cycle(*one_range)) return new_ranges def get_index_for_date(self, the_date): if self.cycle == RentCycle.APRIL_TO_MARCH and is_date_on_first_quarter( the_date): return Index.objects.get_latest_for_year(the_date.year - 1) return Index.objects.get_latest_for_date(the_date)
class Payment(models.Model): """A model that represents a single payment. This might be a transactable payment information such as credit card details, gift card information or a customer's authorization to charge their PayPal account. All payment process related pieces of information are stored at the gateway level, we are operating on the reusable token which is a unique identifier of the customer for given gateway. Several payment methods can be used within a single order. Each payment method may consist of multiple transactions. """ gateway = models.CharField(max_length=255) is_active = models.BooleanField(default=True) to_confirm = models.BooleanField(default=False) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) charge_status = models.CharField(max_length=20, choices=ChargeStatus.CHOICES, default=ChargeStatus.NOT_CHARGED) token = models.CharField(max_length=512, blank=True, default="") total = models.DecimalField( max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, default=Decimal("0.0"), ) captured_amount = models.DecimalField( max_digits=settings.DEFAULT_MAX_DIGITS, decimal_places=settings.DEFAULT_DECIMAL_PLACES, default=Decimal("0.0"), ) currency = models.CharField( max_length=settings.DEFAULT_CURRENCY_CODE_LENGTH ) # FIXME: add ISO4217 validator checkout = models.ForeignKey(Checkout, null=True, related_name="payments", on_delete=models.SET_NULL) order = models.ForeignKey("order.Order", null=True, related_name="payments", on_delete=models.PROTECT) billing_email = models.EmailField(blank=True) billing_first_name = models.CharField(max_length=256, blank=True) billing_last_name = models.CharField(max_length=256, blank=True) billing_company_name = models.CharField(max_length=256, blank=True) billing_address_1 = models.CharField(max_length=256, blank=True) billing_address_2 = models.CharField(max_length=256, blank=True) billing_city = models.CharField(max_length=256, blank=True) billing_city_area = models.CharField(max_length=128, blank=True) billing_postal_code = models.CharField(max_length=256, blank=True) billing_country_code = models.CharField(max_length=2, blank=True) billing_country_area = models.CharField(max_length=256, blank=True) cc_first_digits = models.CharField(max_length=6, blank=True, default="") cc_last_digits = models.CharField(max_length=4, blank=True, default="") cc_brand = models.CharField(max_length=40, blank=True, default="") cc_exp_month = models.PositiveIntegerField( validators=[MinValueValidator(1), MaxValueValidator(12)], null=True, blank=True) cc_exp_year = models.PositiveIntegerField( validators=[MinValueValidator(1000)], null=True, blank=True) payment_method_type = models.CharField(max_length=256, blank=True) customer_ip_address = models.GenericIPAddressField(blank=True, null=True) extra_data = models.TextField(blank=True, default="") return_url = models.URLField(blank=True, null=True) class Meta: ordering = ("pk", ) def __repr__(self): return "Payment(gateway=%s, is_active=%s, created=%s, charge_status=%s)" % ( self.gateway, self.is_active, self.created, self.charge_status, ) def get_last_transaction(self): return max(self.transactions.all(), default=None, key=attrgetter("pk")) def get_total(self): return Money(self.total, self.currency) def get_authorized_amount(self): money = zero_money(self.currency) # Query all the transactions which should be prefetched # to optimize db queries transactions = self.transactions.all() # There is no authorized amount anymore when capture is succeeded # since capture can only be made once, even it is a partial capture if any([ txn.kind == TransactionKind.CAPTURE and txn.is_success for txn in transactions ]): return money # Filter the succeeded auth transactions authorized_txns = [ txn for txn in transactions if txn.kind == TransactionKind.AUTH and txn.is_success and not txn.action_required ] # Calculate authorized amount from all succeeded auth transactions for txn in authorized_txns: money += Money(txn.amount, self.currency) # If multiple partial capture is supported later though it's unlikely, # the authorized amount should exclude the already captured amount here return money def get_captured_amount(self): return Money(self.captured_amount, self.currency) def get_charge_amount(self): """Retrieve the maximum capture possible.""" return self.total - self.captured_amount @property def is_authorized(self): return any([ txn.kind == TransactionKind.AUTH and txn.is_success and not txn.action_required for txn in self.transactions.all() ]) @property def not_charged(self): return self.charge_status == ChargeStatus.NOT_CHARGED def can_authorize(self): return self.is_active and self.not_charged def can_capture(self): if not (self.is_active and self.not_charged): return False return True def can_void(self): return self.is_active and self.not_charged and self.is_authorized def can_refund(self): can_refund_charge_status = ( ChargeStatus.PARTIALLY_CHARGED, ChargeStatus.FULLY_CHARGED, ChargeStatus.PARTIALLY_REFUNDED, ) return self.is_active and self.charge_status in can_refund_charge_status def can_confirm(self): return self.is_active and self.not_charged def is_manual(self): return self.gateway == CustomPaymentChoices.MANUAL
class NPANXX(models.Model): npa = models.PositiveSmallIntegerField( validators=[MinValueValidator(100), MaxValueValidator(999)], verbose_name="NPA") nxx = models.PositiveSmallIntegerField( validators=[MinValueValidator(100), MaxValueValidator(999)], verbose_name="NXX") manual_entry = models.BooleanField(editable=False, default=True) local_to = models.ManyToManyField('self', through='NPANXXIsLocal', symmetrical=False) region = models.ForeignKey('prefix.Region', on_delete=PROTECT, editable=False, blank=True) notes = models.TextField(blank=True) def fetch_local_to(self): prefixes = get_local_prefixes(npa=self.npa, nxx=self.nxx) for prefix in prefixes: npanxx, created = NPANXX.objects.get_or_create( npa=prefix['npa'], nxx=prefix['nxx'], defaults={'manual_entry': False}) NPANXXIsLocal.objects.get_or_create(npanxx_src=self, npanxx_dest=npanxx) NPANXXIsLocal.objects.get_or_create(npanxx_src=npanxx, npanxx_dest=self) @property def local_to_as_list(self): return [ npanxx.npa * 1000 + npanxx.nxx for npanxx in self.local_to.order_by('npa', 'nxx') ] @property def local_to_npa_list(self): return [ npanxx['npa'] for npanxx in self.local_to.order_by('npa').values( 'npa').distinct() ] def get_local_to_as_list_from_npa(self, npa): return [ npanxx.nxx for npanxx in self.local_to.filter(npa=npa).order_by('nxx') ] @property def local_to_prefixes_as_dict(self): return_data = dict() npa_list = self.local_to_npa_list for npa in npa_list: prefixes = self.get_local_to_as_list_from_npa(npa) return_data[npa] = get_ranges_from_iterable(prefixes) return return_data def save(self, *args, **kwargs): npa = get_npa_data(npa=self.npa) region, created = Region.objects.get_or_create(name=npa['region'], long_name=npa['rname']) self.region = region super().save(*args, **kwargs) def __str__(self): return "({npa}) {nxx}".format(npa=self.npa, nxx=self.nxx) class Meta: unique_together = (("npa", "nxx"), ) verbose_name = 'NPANXX' verbose_name_plural = 'NPANXXes'
class Crab(models.Model): sample_num = models.IntegerField(default=0) # used to find the crab image # number of oocytes that reached more than 10 clicks done_oocytes = models.IntegerField(default=0, validators=[MaxValueValidator(10)]) year = models.IntegerField(default=datetime.date.today().year) latitude = models.FloatField() longitude = models.FloatField() water_temp = models.FloatField() shell_condition = models.IntegerField() #Called by a crab when it has 10 completed oocytes. It will then send its data to socrata. def send_crab_data(self): CONVERSION_RATE = .00000701549 oocytes = Oocyte.objects.filter(crab=self).filter(chosen_count=10) client = Socrata("noaa-fisheries-afsc.data.socrata.com", "q3DhSQxvyWbtq1kLPs5q7jwQp", username="******", password="******") data = { 'area_2': '', 'area_5': '', 'calibration_5x': 0.00028, 'area_4': '', 'area_7': '', 'area_10': '', 'calibration_10x': 0.00056, 'area_9': '', 'year': '', 'sample': '', 'area_3': '', 'area_8': '', 'area_1': '', 'area_6': '' } data['area_1'] = oocytes[0].area * CONVERSION_RATE data['area_2'] = oocytes[1].area * CONVERSION_RATE data['area_3'] = oocytes[2].area * CONVERSION_RATE data['area_4'] = oocytes[3].area * CONVERSION_RATE data['area_5'] = oocytes[4].area * CONVERSION_RATE data['area_6'] = oocytes[5].area * CONVERSION_RATE data['area_7'] = oocytes[6].area * CONVERSION_RATE data['area_8'] = oocytes[7].area * CONVERSION_RATE data['area_9'] = oocytes[8].area * CONVERSION_RATE data['area_10'] = oocytes[9].area * CONVERSION_RATE data['year'] = datetime.datetime.now().year data['sample'] = self.sample_num payload = [data] client.upsert("km2u-hwjw", payload) #creates a new crab, finds all of its images and adds them to the system #also imports oocyte instances for each image #find path, for each folder, create crab, read its data from socrata, read all the images on that path @classmethod def create_image_instances(cls, path): print(path) for root, dirs, files in os.walk(path, topdown=False): for folder in dirs: sn = int(folder) #If the crab is not already in the system, then create it if (Crab.objects.filter(sample_num=sn).count() == 0): client = Socrata("noaa-fisheries-afsc.data.socrata.com", "q3DhSQxvyWbtq1kLPs5q7jwQp", username="******", password="******") crab_info = client.get("n49y-v5db", where=("sample = " + str(sn))) lat, lon = crab_info[0]['location_1'][ 'latitude'], crab_info[0]['location_1']['longitude'] yr, wt = crab_info[0]['year'], crab_info[0][ 'bottom_temp_c'] sc = crab_info[0]['shell_condition'] crab = Crab(sample_num=sn, year=yr, longitude=lon, latitude=lat, water_temp=wt, shell_condition=sc) #This path would be where all the images are stored locally before upload #Python script should be pushing images to this path along with its csv file if (path[-1:] == '/'): image_folder = path + str(sn) else: image_folder = path + '/' + str(sn) crab.save() for filename in os.listdir(image_folder): #look for a resized image and then find its labeled counterpart if (filename[-12:] == "_resized.png"): #tag is the identifier for that image tag = filename[:-12] #open both original and resized orig = File( open(image_folder + '/' + tag + '_resized.png', 'rb')) label = File( open(image_folder + '/' + tag + '_labeled.png', 'rb')) data = tag + "_area.csv" #create Image instance and save images to the media root directory image = Image(crab=crab, csv=data) image.original_img.save(tag + "_resize.png", orig, save=False) image.binarized_img.save(tag + "_label.png", label, save=False) image.save() #read csv for image and import new oocyte instances #csv must be located in the original path directory where the images were stored with open(image_folder + '/' + data, 'r', newline='') as csvfile: areareader = csv.reader(csvfile, delimiter=',', quotechar='|') for row in areareader: area, xcenter, ycenter = row[0], row[ 1], row[2] Oocyte.objects.create(crab=crab, image=image, area=area, center_x=xcenter, center_y=ycenter) def __str__(self): return ("crab (pk=" + str(self.id) + ", sample_num=" + str(self.sample_num) + ")")
class Rating(models.Model): Show = models.ForeignKey(Show, on_delete=models.CASCADE) User = models.ForeignKey(User, on_delete=models.CASCADE) Stars = models.IntegerField( validators=[MaxValueValidator(10), MinValueValidator(1)])
def max_value_current_year(value): return MaxValueValidator(current_year())(value)
class Check(BaseAuditModel): # common fields agent = models.ForeignKey( "agents.Agent", related_name="agentchecks", null=True, blank=True, on_delete=models.CASCADE, ) policy = models.ForeignKey( "automation.Policy", related_name="policychecks", null=True, blank=True, on_delete=models.CASCADE, ) managed_by_policy = models.BooleanField(default=False) overriden_by_policy = models.BooleanField(default=False) parent_check = models.PositiveIntegerField(null=True, blank=True) name = models.CharField(max_length=255, null=True, blank=True) check_type = models.CharField(max_length=50, choices=CHECK_TYPE_CHOICES, default="diskspace") status = models.CharField(max_length=100, choices=CHECK_STATUS_CHOICES, default="pending") more_info = models.TextField(null=True, blank=True) last_run = models.DateTimeField(null=True, blank=True) email_alert = models.BooleanField(default=False) text_alert = models.BooleanField(default=False) dashboard_alert = models.BooleanField(default=False) fails_b4_alert = models.PositiveIntegerField(default=1) fail_count = models.PositiveIntegerField(default=0) outage_history = models.JSONField(null=True, blank=True) # store extra_details = models.JSONField(null=True, blank=True) # check specific fields # for eventlog, script, ip, and service alert severity alert_severity = models.CharField( max_length=15, choices=SEVERITY_CHOICES, default="warning", null=True, blank=True, ) # threshold percent for diskspace, cpuload or memory check error_threshold = models.PositiveIntegerField( validators=[MinValueValidator(0), MaxValueValidator(99)], null=True, blank=True, default=0, ) warning_threshold = models.PositiveIntegerField( null=True, blank=True, validators=[MinValueValidator(0), MaxValueValidator(99)], default=0, ) # diskcheck i.e C:, D: etc disk = models.CharField(max_length=2, null=True, blank=True) # ping checks ip = models.CharField(max_length=255, null=True, blank=True) # script checks script = models.ForeignKey( "scripts.Script", related_name="script", on_delete=models.CASCADE, null=True, blank=True, ) script_args = ArrayField( models.CharField(max_length=255, null=True, blank=True), null=True, blank=True, default=list, ) info_return_codes = ArrayField( models.PositiveIntegerField(), null=True, blank=True, default=list, ) warning_return_codes = ArrayField( models.PositiveIntegerField(), null=True, blank=True, default=list, ) timeout = models.PositiveIntegerField(null=True, blank=True) stdout = models.TextField(null=True, blank=True) stderr = models.TextField(null=True, blank=True) retcode = models.IntegerField(null=True, blank=True) execution_time = models.CharField(max_length=100, null=True, blank=True) # cpu and mem check history history = ArrayField(models.IntegerField(blank=True), null=True, blank=True, default=list) # win service checks svc_name = models.CharField(max_length=255, null=True, blank=True) svc_display_name = models.CharField(max_length=255, null=True, blank=True) pass_if_start_pending = models.BooleanField(null=True, blank=True) pass_if_svc_not_exist = models.BooleanField(default=False) restart_if_stopped = models.BooleanField(null=True, blank=True) svc_policy_mode = models.CharField( max_length=20, null=True, blank=True) # 'default' or 'manual', for editing policy check # event log checks log_name = models.CharField(max_length=255, choices=EVT_LOG_NAME_CHOICES, null=True, blank=True) event_id = models.IntegerField(null=True, blank=True) event_id_is_wildcard = models.BooleanField(default=False) event_type = models.CharField(max_length=255, choices=EVT_LOG_TYPE_CHOICES, null=True, blank=True) event_source = models.CharField(max_length=255, null=True, blank=True) event_message = models.TextField(null=True, blank=True) fail_when = models.CharField(max_length=255, choices=EVT_LOG_FAIL_WHEN_CHOICES, null=True, blank=True) search_last_days = models.PositiveIntegerField(null=True, blank=True) def __str__(self): if self.agent: return f"{self.agent.hostname} - {self.readable_desc}" else: return f"{self.policy.name} - {self.readable_desc}" @property def readable_desc(self): if self.check_type == "diskspace": text = "" if self.warning_threshold: text += f" Warning Threshold: {self.warning_threshold}%" if self.error_threshold: text += f" Error Threshold: {self.error_threshold}%" return f"{self.get_check_type_display()}: Drive {self.disk} - {text}" # type: ignore elif self.check_type == "ping": return f"{self.get_check_type_display()}: {self.name}" # type: ignore elif self.check_type == "cpuload" or self.check_type == "memory": text = "" if self.warning_threshold: text += f" Warning Threshold: {self.warning_threshold}%" if self.error_threshold: text += f" Error Threshold: {self.error_threshold}%" return f"{self.get_check_type_display()} - {text}" # type: ignore elif self.check_type == "winsvc": return f"{self.get_check_type_display()}: {self.svc_display_name}" # type: ignore elif self.check_type == "eventlog": return f"{self.get_check_type_display()}: {self.name}" # type: ignore elif self.check_type == "script": return f"{self.get_check_type_display()}: {self.script.name}" # type: ignore else: return "n/a" @property def history_info(self): if self.check_type == "cpuload" or self.check_type == "memory": return ", ".join(str(f"{x}%") for x in self.history[-6:]) @property def last_run_as_timezone(self): if self.last_run is not None and self.agent is not None: return self.last_run.astimezone(pytz.timezone( self.agent.timezone)).strftime("%b-%d-%Y - %H:%M") return self.last_run @property def non_editable_fields(self) -> list[str]: return [ "check_type", "status", "more_info", "last_run", "fail_count", "outage_history", "extra_details", "stdout", "stderr", "retcode", "execution_time", "history", "readable_desc", "history_info", "parent_check", "managed_by_policy", "overriden_by_policy", "created_by", "created_time", "modified_by", "modified_time", ] def should_create_alert(self, alert_template): return (self.dashboard_alert or self.email_alert or self.text_alert or (alert_template and (alert_template.check_always_alert or alert_template.check_always_email or alert_template.check_always_text))) def add_check_history(self, value: int, more_info: Any = None) -> None: CheckHistory.objects.create(check_history=self, y=value, results=more_info) def handle_checkv2(self, data): from alerts.models import Alert # cpuload or mem checks if self.check_type == "cpuload" or self.check_type == "memory": self.history.append(data["percent"]) if len(self.history) > 15: self.history = self.history[-15:] self.save(update_fields=["history"]) avg = int(mean(self.history)) if self.error_threshold and avg > self.error_threshold: self.status = "failing" self.alert_severity = "error" elif self.warning_threshold and avg > self.warning_threshold: self.status = "failing" self.alert_severity = "warning" else: self.status = "passing" # add check history self.add_check_history(data["percent"]) # diskspace checks elif self.check_type == "diskspace": if data["exists"]: percent_used = round(data["percent_used"]) total = bytes2human(data["total"]) free = bytes2human(data["free"]) if self.error_threshold and ( 100 - percent_used) < self.error_threshold: self.status = "failing" self.alert_severity = "error" elif (self.warning_threshold and (100 - percent_used) < self.warning_threshold): self.status = "failing" self.alert_severity = "warning" else: self.status = "passing" self.more_info = f"Total: {total}B, Free: {free}B" # add check history self.add_check_history(100 - percent_used) else: self.status = "failing" self.alert_severity = "error" self.more_info = f"Disk {self.disk} does not exist" self.save(update_fields=["more_info"]) # script checks elif self.check_type == "script": self.stdout = data["stdout"] self.stderr = data["stderr"] self.retcode = data["retcode"] try: # python agent self.execution_time = "{:.4f}".format(data["stop"] - data["start"]) except: # golang agent self.execution_time = "{:.4f}".format(data["runtime"]) if data["retcode"] in self.info_return_codes: self.alert_severity = "info" self.status = "failing" elif data["retcode"] in self.warning_return_codes: self.alert_severity = "warning" self.status = "failing" elif data["retcode"] != 0: self.status = "failing" self.alert_severity = "error" else: self.status = "passing" self.save(update_fields=[ "stdout", "stderr", "retcode", "execution_time", ]) # add check history self.add_check_history( 1 if self.status == "failing" else 0, { "retcode": data["retcode"], "stdout": data["stdout"][:60], "stderr": data["stderr"][:60], "execution_time": self.execution_time, }, ) # ping checks elif self.check_type == "ping": success = ["Reply", "bytes", "time", "TTL"] output = data["output"] if data["has_stdout"]: if all(x in output for x in success): self.status = "passing" else: self.status = "failing" elif data["has_stderr"]: self.status = "failing" self.more_info = output self.save(update_fields=["more_info"]) self.add_check_history(1 if self.status == "failing" else 0, self.more_info[:60]) # windows service checks elif self.check_type == "winsvc": svc_stat = data["status"] self.more_info = f"Status {svc_stat.upper()}" if data["exists"]: if svc_stat == "running": self.status = "passing" elif svc_stat == "start_pending" and self.pass_if_start_pending: self.status = "passing" else: if self.agent and self.restart_if_stopped: nats_data = { "func": "winsvcaction", "payload": { "name": self.svc_name, "action": "start" }, } r = asyncio.run( self.agent.nats_cmd(nats_data, timeout=32)) if r == "timeout" or r == "natsdown": self.status = "failing" elif not r["success"] and r["errormsg"]: self.status = "failing" elif r["success"]: self.status = "passing" self.more_info = f"Status RUNNING" else: self.status = "failing" else: self.status = "failing" else: if self.pass_if_svc_not_exist: self.status = "passing" else: self.status = "failing" self.more_info = f"Service {self.svc_name} does not exist" self.save(update_fields=["more_info"]) self.add_check_history(1 if self.status == "failing" else 0, self.more_info[:60]) elif self.check_type == "eventlog": log = [] is_wildcard = self.event_id_is_wildcard eventType = self.event_type eventID = self.event_id source = self.event_source message = self.event_message r = data["log"] for i in r: if i["eventType"] == eventType: if not is_wildcard and not int(i["eventID"]) == eventID: continue if not source and not message: if is_wildcard: log.append(i) elif int(i["eventID"]) == eventID: log.append(i) continue if source and message: if is_wildcard: if source in i["source"] and message in i[ "message"]: log.append(i) elif int(i["eventID"]) == eventID: if source in i["source"] and message in i[ "message"]: log.append(i) continue if source and source in i["source"]: if is_wildcard: log.append(i) elif int(i["eventID"]) == eventID: log.append(i) if message and message in i["message"]: if is_wildcard: log.append(i) elif int(i["eventID"]) == eventID: log.append(i) if self.fail_when == "contains": if log: self.status = "failing" else: self.status = "passing" elif self.fail_when == "not_contains": if log: self.status = "passing" else: self.status = "failing" self.extra_details = {"log": log} self.save(update_fields=["extra_details"]) self.add_check_history( 1 if self.status == "failing" else 0, "Events Found:" + str(len(self.extra_details["log"])), ) # handle status if self.status == "failing": self.fail_count += 1 self.save(update_fields=["status", "fail_count", "alert_severity"]) if self.fail_count >= self.fails_b4_alert: Alert.handle_alert_failure(self) elif self.status == "passing": self.fail_count = 0 self.save(update_fields=["status", "fail_count", "alert_severity"]) if Alert.objects.filter(assigned_check=self, resolved=False).exists(): Alert.handle_alert_resolve(self) return self.status @staticmethod def serialize(check): # serializes the check and returns json from .serializers import CheckSerializer return CheckSerializer(check).data # for policy diskchecks @staticmethod def all_disks(): return [f"{i}:" for i in string.ascii_uppercase] # for policy service checks @staticmethod def load_default_services(): with open( os.path.join(settings.BASE_DIR, "services/default_services.json")) as f: default_services = json.load(f) return default_services def create_policy_check(self, agent=None, policy=None): if not agent and not policy or agent and policy: return Check.objects.create( agent=agent, policy=policy, managed_by_policy=bool(agent), parent_check=(self.pk if agent else None), name=self.name, alert_severity=self.alert_severity, check_type=self.check_type, email_alert=self.email_alert, dashboard_alert=self.dashboard_alert, text_alert=self.text_alert, fails_b4_alert=self.fails_b4_alert, extra_details=self.extra_details, error_threshold=self.error_threshold, warning_threshold=self.warning_threshold, disk=self.disk, ip=self.ip, script=self.script, script_args=self.script_args, timeout=self.timeout, info_return_codes=self.info_return_codes, warning_return_codes=self.warning_return_codes, svc_name=self.svc_name, svc_display_name=self.svc_display_name, pass_if_start_pending=self.pass_if_start_pending, pass_if_svc_not_exist=self.pass_if_svc_not_exist, restart_if_stopped=self.restart_if_stopped, svc_policy_mode=self.svc_policy_mode, log_name=self.log_name, event_id=self.event_id, event_id_is_wildcard=self.event_id_is_wildcard, event_type=self.event_type, event_source=self.event_source, event_message=self.event_message, fail_when=self.fail_when, search_last_days=self.search_last_days, ) def is_duplicate(self, check): if self.check_type == "diskspace": return self.disk == check.disk elif self.check_type == "script": return self.script == check.script elif self.check_type == "ping": return self.ip == check.ip elif self.check_type == "cpuload": return True elif self.check_type == "memory": return True elif self.check_type == "winsvc": return self.svc_name == check.svc_name elif self.check_type == "eventlog": return [self.log_name, self.event_id] == [check.log_name, check.event_id] def send_email(self): CORE = CoreSettings.objects.first() alert_template = self.agent.get_alert_template() body: str = "" if self.agent: subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Failed" else: subject = f"{self} Failed" if self.check_type == "diskspace": text = "" if self.warning_threshold: text += f" Warning Threshold: {self.warning_threshold}%" if self.error_threshold: text += f" Error Threshold: {self.error_threshold}%" percent_used = [ d["percent"] for d in self.agent.disks if d["device"] == self.disk ][0] percent_free = 100 - percent_used body = subject + f" - Free: {percent_free}%, {text}" elif self.check_type == "script": body = ( subject + f" - Return code: {self.retcode}\nStdout:{self.stdout}\nStderr: {self.stderr}" ) elif self.check_type == "ping": body = self.more_info elif self.check_type == "cpuload" or self.check_type == "memory": text = "" if self.warning_threshold: text += f" Warning Threshold: {self.warning_threshold}%" if self.error_threshold: text += f" Error Threshold: {self.error_threshold}%" avg = int(mean(self.history)) if self.check_type == "cpuload": body = subject + f" - Average CPU utilization: {avg}%, {text}" elif self.check_type == "memory": body = subject + f" - Average memory usage: {avg}%, {text}" elif self.check_type == "winsvc": try: status = list( filter(lambda x: x["name"] == self.svc_name, self.agent.services))[0]["status"] # catch services that don't exist if policy check except: status = "Unknown" body = subject + f" - Status: {status.upper()}" elif self.check_type == "eventlog": if self.event_source and self.event_message: start = f"Event ID {self.event_id}, source {self.event_source}, containing string {self.event_message} " elif self.event_source: start = f"Event ID {self.event_id}, source {self.event_source} " elif self.event_message: start = ( f"Event ID {self.event_id}, containing string {self.event_message} " ) else: start = f"Event ID {self.event_id} " body = start + f"was found in the {self.log_name} log\n\n" for i in self.extra_details["log"]: try: if i["message"]: body += f"{i['message']}\n" except: continue CORE.send_mail(subject, body, alert_template=alert_template) def send_sms(self): CORE = CoreSettings.objects.first() alert_template = self.agent.get_alert_template() body: str = "" if self.agent: subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Failed" else: subject = f"{self} Failed" if self.check_type == "diskspace": text = "" if self.warning_threshold: text += f" Warning Threshold: {self.warning_threshold}%" if self.error_threshold: text += f" Error Threshold: {self.error_threshold}%" percent_used = [ d["percent"] for d in self.agent.disks if d["device"] == self.disk ][0] percent_free = 100 - percent_used body = subject + f" - Free: {percent_free}%, {text}" elif self.check_type == "script": body = subject + f" - Return code: {self.retcode}" elif self.check_type == "ping": body = subject elif self.check_type == "cpuload" or self.check_type == "memory": text = "" if self.warning_threshold: text += f" Warning Threshold: {self.warning_threshold}%" if self.error_threshold: text += f" Error Threshold: {self.error_threshold}%" avg = int(mean(self.history)) if self.check_type == "cpuload": body = subject + f" - Average CPU utilization: {avg}%, {text}" elif self.check_type == "memory": body = subject + f" - Average memory usage: {avg}%, {text}" elif self.check_type == "winsvc": status = list( filter(lambda x: x["name"] == self.svc_name, self.agent.services))[0]["status"] body = subject + f" - Status: {status.upper()}" elif self.check_type == "eventlog": body = subject CORE.send_sms(body, alert_template=alert_template) def send_resolved_email(self): CORE = CoreSettings.objects.first() alert_template = self.agent.get_alert_template() subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Resolved" body = f"{self} is now back to normal" CORE.send_mail(subject, body, alert_template=alert_template) def send_resolved_sms(self): CORE = CoreSettings.objects.first() alert_template = self.agent.get_alert_template() subject = f"{self.agent.client.name}, {self.agent.site.name}, {self} Resolved" CORE.send_sms(subject, alert_template=alert_template)
class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): """ A Device represents a piece of physical hardware mounted within a Rack. Each Device is assigned a DeviceType, DeviceRole, and (optionally) a Platform. Device names are not required, however if one is set it must be unique. Each Device must be assigned to a site, and optionally to a rack within that site. Associating a device with a particular rack face or unit is optional (for example, vertically mounted PDUs do not consume rack units). When a new Device is created, console/power/interface/device bay components are created along with it as dictated by the component templates assigned to its DeviceType. Components can also be added, modified, or deleted after the creation of a Device. """ device_type = models.ForeignKey(to='dcim.DeviceType', on_delete=models.PROTECT, related_name='instances') device_role = models.ForeignKey(to='dcim.DeviceRole', on_delete=models.PROTECT, related_name='devices') tenant = models.ForeignKey(to='tenancy.Tenant', on_delete=models.PROTECT, related_name='devices', blank=True, null=True) platform = models.ForeignKey(to='dcim.Platform', on_delete=models.SET_NULL, related_name='devices', blank=True, null=True) name = models.CharField(max_length=64, blank=True, null=True) _name = NaturalOrderingField(target_field='name', max_length=100, blank=True, null=True) serial = models.CharField(max_length=50, blank=True, verbose_name='Serial number') asset_tag = models.CharField( max_length=50, blank=True, null=True, unique=True, verbose_name='Asset tag', help_text='A unique tag used to identify this device') site = models.ForeignKey(to='dcim.Site', on_delete=models.PROTECT, related_name='devices') rack = models.ForeignKey(to='dcim.Rack', on_delete=models.PROTECT, related_name='devices', blank=True, null=True) position = models.PositiveSmallIntegerField( blank=True, null=True, validators=[MinValueValidator(1)], verbose_name='Position (U)', help_text='The lowest-numbered unit occupied by the device') face = models.CharField(max_length=50, blank=True, choices=DeviceFaceChoices, verbose_name='Rack face') status = models.CharField(max_length=50, choices=DeviceStatusChoices, default=DeviceStatusChoices.STATUS_ACTIVE) primary_ip4 = models.OneToOneField(to='ipam.IPAddress', on_delete=models.SET_NULL, related_name='primary_ip4_for', blank=True, null=True, verbose_name='Primary IPv4') primary_ip6 = models.OneToOneField(to='ipam.IPAddress', on_delete=models.SET_NULL, related_name='primary_ip6_for', blank=True, null=True, verbose_name='Primary IPv6') cluster = models.ForeignKey(to='virtualization.Cluster', on_delete=models.SET_NULL, related_name='devices', blank=True, null=True) virtual_chassis = models.ForeignKey(to='VirtualChassis', on_delete=models.SET_NULL, related_name='members', blank=True, null=True) vc_position = models.PositiveSmallIntegerField( blank=True, null=True, validators=[MaxValueValidator(255)]) vc_priority = models.PositiveSmallIntegerField( blank=True, null=True, validators=[MaxValueValidator(255)]) comments = models.TextField(blank=True) custom_field_values = GenericRelation(to='extras.CustomFieldValue', content_type_field='obj_type', object_id_field='obj_id') images = GenericRelation(to='extras.ImageAttachment') tags = TaggableManager(through=TaggedItem) objects = RestrictedQuerySet.as_manager() csv_headers = [ 'name', 'device_role', 'tenant', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'status', 'site', 'rack_group', 'rack_name', 'position', 'face', 'comments', ] clone_fields = [ 'device_type', 'device_role', 'tenant', 'platform', 'site', 'rack', 'status', 'cluster', ] STATUS_CLASS_MAP = { DeviceStatusChoices.STATUS_OFFLINE: 'warning', DeviceStatusChoices.STATUS_ACTIVE: 'success', DeviceStatusChoices.STATUS_PLANNED: 'info', DeviceStatusChoices.STATUS_STAGED: 'primary', DeviceStatusChoices.STATUS_FAILED: 'danger', DeviceStatusChoices.STATUS_INVENTORY: 'default', DeviceStatusChoices.STATUS_DECOMMISSIONING: 'warning', } class Meta: ordering = ('_name', 'pk') # Name may be null unique_together = ( ('site', 'tenant', 'name'), # See validate_unique below ('rack', 'position', 'face'), ('virtual_chassis', 'vc_position'), ) def __str__(self): return self.display_name or super().__str__() def get_absolute_url(self): return reverse('dcim:device', args=[self.pk]) def validate_unique(self, exclude=None): # Check for a duplicate name on a device assigned to the same Site and no Tenant. This is necessary # because Django does not consider two NULL fields to be equal, and thus will not trigger a violation # of the uniqueness constraint without manual intervention. if self.name and hasattr(self, 'site') and self.tenant is None: if Device.objects.exclude(pk=self.pk).filter(name=self.name, site=self.site, tenant__isnull=True): raise ValidationError( {'name': 'A device with this name already exists.'}) super().validate_unique(exclude) def clean(self): super().clean() # Validate site/rack combination if self.rack and self.site != self.rack.site: raise ValidationError({ 'rack': "Rack {} does not belong to site {}.".format( self.rack, self.site), }) if self.rack is None: if self.face: raise ValidationError({ 'face': "Cannot select a rack face without assigning a rack.", }) if self.position: raise ValidationError({ 'face': "Cannot select a rack position without assigning a rack.", }) # Validate position/face combination if self.position and not self.face: raise ValidationError({ 'face': "Must specify rack face when defining rack position.", }) # Prevent 0U devices from being assigned to a specific position if self.position and self.device_type.u_height == 0: raise ValidationError({ 'position': "A U0 device type ({}) cannot be assigned to a rack position.". format(self.device_type) }) if self.rack: try: # Child devices cannot be assigned to a rack face/unit if self.device_type.is_child_device and self.face: raise ValidationError({ 'face': "Child device types cannot be assigned to a rack face. This is an attribute of the " "parent device." }) if self.device_type.is_child_device and self.position: raise ValidationError({ 'position': "Child device types cannot be assigned to a rack position. This is an attribute of " "the parent device." }) # Validate rack space rack_face = self.face if not self.device_type.is_full_depth else None exclude_list = [self.pk] if self.pk else [] available_units = self.rack.get_available_units( u_height=self.device_type.u_height, rack_face=rack_face, exclude=exclude_list) if self.position and self.position not in available_units: raise ValidationError({ 'position': "U{} is already occupied or does not have sufficient space to accommodate a(n) " "{} ({}U).".format(self.position, self.device_type, self.device_type.u_height) }) except DeviceType.DoesNotExist: pass # Validate primary IP addresses vc_interfaces = self.vc_interfaces.all() if self.primary_ip4: if self.primary_ip4.family != 4: raise ValidationError({ 'primary_ip4': f"{self.primary_ip4} is not an IPv4 address." }) if self.primary_ip4.assigned_object in vc_interfaces: pass elif self.primary_ip4.nat_inside is not None and self.primary_ip4.nat_inside.assigned_object in vc_interfaces: pass else: raise ValidationError({ 'primary_ip4': f"The specified IP address ({self.primary_ip4}) is not assigned to this device." }) if self.primary_ip6: if self.primary_ip6.family != 6: raise ValidationError({ 'primary_ip6': f"{self.primary_ip6} is not an IPv6 address." }) if self.primary_ip6.assigned_object in vc_interfaces: pass elif self.primary_ip6.nat_inside is not None and self.primary_ip6.nat_inside.assigned_object in vc_interfaces: pass else: raise ValidationError({ 'primary_ip6': f"The specified IP address ({self.primary_ip6}) is not assigned to this device." }) # Validate manufacturer/platform if hasattr(self, 'device_type') and self.platform: if self.platform.manufacturer and self.platform.manufacturer != self.device_type.manufacturer: raise ValidationError({ 'platform': "The assigned platform is limited to {} device types, but this device's type belongs " "to {}.".format(self.platform.manufacturer, self.device_type.manufacturer) }) # A Device can only be assigned to a Cluster in the same Site (or no Site) if self.cluster and self.cluster.site is not None and self.cluster.site != self.site: raise ValidationError({ 'cluster': "The assigned cluster belongs to a different site ({})".format( self.cluster.site) }) # Validate virtual chassis assignment if self.virtual_chassis and self.vc_position is None: raise ValidationError({ 'vc_position': "A device assigned to a virtual chassis must have its position defined." }) def save(self, *args, **kwargs): is_new = not bool(self.pk) super().save(*args, **kwargs) # If this is a new Device, instantiate all of the related components per the DeviceType definition if is_new: ConsolePort.objects.bulk_create([ x.instantiate(self) for x in self.device_type.consoleporttemplates.all() ]) ConsoleServerPort.objects.bulk_create([ x.instantiate(self) for x in self.device_type.consoleserverporttemplates.all() ]) PowerPort.objects.bulk_create([ x.instantiate(self) for x in self.device_type.powerporttemplates.all() ]) PowerOutlet.objects.bulk_create([ x.instantiate(self) for x in self.device_type.poweroutlettemplates.all() ]) Interface.objects.bulk_create([ x.instantiate(self) for x in self.device_type.interfacetemplates.all() ]) RearPort.objects.bulk_create([ x.instantiate(self) for x in self.device_type.rearporttemplates.all() ]) FrontPort.objects.bulk_create([ x.instantiate(self) for x in self.device_type.frontporttemplates.all() ]) DeviceBay.objects.bulk_create([ x.instantiate(self) for x in self.device_type.devicebaytemplates.all() ]) # Update Site and Rack assignment for any child Devices devices = Device.objects.filter(parent_bay__device=self) for device in devices: device.site = self.site device.rack = self.rack device.save() def to_csv(self): return ( self.name or '', self.device_role.name, self.tenant.name if self.tenant else None, self.device_type.manufacturer.name, self.device_type.model, self.platform.name if self.platform else None, self.serial, self.asset_tag, self.get_status_display(), self.site.name, self.rack.group.name if self.rack and self.rack.group else None, self.rack.name if self.rack else None, self.position, self.get_face_display(), self.comments, ) @property def display_name(self): if self.name: return self.name elif self.virtual_chassis: return f'{self.virtual_chassis.name}:{self.vc_position} ({self.pk})' elif self.device_type: return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})' else: return '' # Device has not yet been created @property def identifier(self): """ Return the device name if set; otherwise return the Device's primary key as {pk} """ if self.name is not None: return self.name return '{{{}}}'.format(self.pk) @property def primary_ip(self): if settings.PREFER_IPV4 and self.primary_ip4: return self.primary_ip4 elif self.primary_ip6: return self.primary_ip6 elif self.primary_ip4: return self.primary_ip4 else: return None def get_vc_master(self): """ If this Device is a VirtualChassis member, return the VC master. Otherwise, return None. """ return self.virtual_chassis.master if self.virtual_chassis else None @property def vc_interfaces(self): """ Return a QuerySet matching all Interfaces assigned to this Device or, if this Device is a VC master, to another Device belonging to the same VirtualChassis. """ filter = Q(device=self) if self.virtual_chassis and self.virtual_chassis.master == self: filter |= Q(device__virtual_chassis=self.virtual_chassis, mgmt_only=False) return Interface.objects.filter(filter) def get_cables(self, pk_list=False): """ Return a QuerySet or PK list matching all Cables connected to a component of this Device. """ cable_pks = [] for component_model in [ ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet, Interface, FrontPort, RearPort ]: cable_pks += component_model.objects.filter( device=self, cable__isnull=False).values_list('cable', flat=True) if pk_list: return cable_pks return Cable.objects.filter(pk__in=cable_pks) def get_children(self): """ Return the set of child Devices installed in DeviceBays within this Device. """ return Device.objects.filter(parent_bay__device=self.pk) def get_status_class(self): return self.STATUS_CLASS_MAP.get(self.status)
class Invoice(models.Model): """ Model representing Invoice itself. It keeps all necessary information described at https://www.gov.uk/vat-record-keeping/vat-invoices """ COUNTER_PERIOD = Choices(('DAILY', _('daily')), ('MONTHLY', _('monthly')), ('YEARLY', _('yearly'))) TYPE = Choices(('INVOICE', _(u'Invoice')), ('ADVANCE', _(u'Advance invoice')), ('PROFORMA', _(u'Proforma invoice')), ('VAT_CREDIT_NOTE', _(u'VAT credit note'))) STATUS = Choices(('NEW', _(u'new')), ('SENT', _(u'sent')), ('RETURNED', _(u'returned')), ('CANCELED', _(u'canceled')), ('PAID', _(u'paid'))) PAYMENT_METHOD = Choices(('BANK_TRANSFER', _(u'bank transfer')), ('CASH', _(u'cash')), ('CASH_ON_DELIVERY', _(u'cash on delivery')), ('PAYMENT_CARD', _(u'payment card'))) DELIVERY_METHOD = Choices( ('PERSONAL_PICKUP', _(u'personal pickup')), ('MAILING', _(u'mailing')), ('DIGITAL', _(u'digital')), ) CONSTANT_SYMBOL = Choices( ('0001', _(u'0001 - Payments for goods based on legal and executable decision from legal authority' )), ('0008', _(u'0008 - Cashless payments for goods')), ('0038', _(u'0038 - Cashless funds for wages')), ('0058', _(u'0058 - Cashless penalty and delinquency charges')), ('0068', _(u'0068 - Transfer of funds for wages and other personal costs')), ('0138', _(u'0138 - Cashless deductions at source')), ('0168', _(u'0168 - Cashless payments in loans')), ('0178', _(u'0178 - Sales from provided services')), ('0298', _(u'0298 - Other cashless transfers')), ('0304', _(u'0304 - Prior payments for services')), ('0308', _(u'0308 - Cashless payments for services')), ('0358', _(u'0358 - Payments dedicated to payout through post offices')), ('0379', _(u'0379 - Other income, income from postal order')), ('0498', _(u'0498 - Payments in loans')), ('0558', _(u'0558 - Cashless other financial payments')), ('0934', _(u'0934 - Benefits - prior payments')), ('0968', _(u'0968 - Other cashless transfers')), ('1144', _(u'1144 - Prior payment - advance')), ('1148', _(u'1148 - Payment - current advance')), ('1744', _(u'1744 - Accounting of tax at income tax of physical body and corporate body' )), ('1748', _(u'1748 - Income tax of physical body and corporate body based on declared tax year' )), ('3118', _(u'3118 - Insurance and empl. contrib. to insur. co. and the Labor Office' )), ('3344', _(u'3344 - Penalty from message - prior')), ('3348', _(u'3348 - Penalty from message')), ('3354', _(u'3354 - Insurance payments by insurance companies')), ('3558', _(u'3558 - Cashless insurance payments by insurance companies')), ('8147', _(u'8147 - Payment (posted together with the instruction)'))) # General information type = models.CharField(_(u'type'), max_length=64, choices=TYPE, default=TYPE.INVOICE) number = models.IntegerField(_(u'number'), db_index=True, blank=True) full_number = models.CharField(max_length=128, blank=True) status = models.CharField(_(u'status'), choices=STATUS, max_length=64, default=STATUS.NEW) subtitle = models.CharField(_(u'subtitle'), max_length=255, blank=True, null=True, default=None) language = models.CharField(_(u'language'), max_length=10, choices=settings.LANGUAGES) note = models.CharField(_(u'note'), max_length=255, blank=True, null=True, default=_(u'Thank you for using our services.')) date_issue = models.DateField(_(u'issue date')) date_tax_point = models.DateField(_(u'tax point date'), help_text=_(u'time of supply')) date_due = models.DateField(_(u'due date'), help_text=_(u'payment till')) date_sent = MonitorField(monitor='status', when=[STATUS.SENT], blank=True, null=True, default=None) # Payment details currency = models.CharField(_(u'currency'), max_length=10, choices=CURRENCY_CHOICES) discount = models.DecimalField(_(u'discount (%)'), max_digits=3, decimal_places=1, default=0) credit = models.DecimalField(_(u'credit'), max_digits=10, decimal_places=2, default=0) #already_paid = models.DecimalField(_(u'already paid'), max_digits=10, decimal_places=2, default=0) payment_method = models.CharField(_(u'payment method'), choices=PAYMENT_METHOD, max_length=64) constant_symbol = models.CharField(_(u'constant symbol'), max_length=64, choices=CONSTANT_SYMBOL, blank=True, null=True, default=None) variable_symbol = models.PositiveIntegerField( _(u'variable symbol'), validators=[MinValueValidator(0), MaxValueValidator(9999999999)], blank=True, null=True, default=None) specific_symbol = models.PositiveIntegerField( _(u'specific symbol'), validators=[MinValueValidator(0), MaxValueValidator(9999999999)], blank=True, null=True, default=None) reference = models.CharField(_(u'reference'), max_length=140, blank=True, null=True, default=None) bank_name = models.CharField(_(u'bank name'), max_length=255, blank=True, null=True, default=None) bank_street = models.CharField(_(u'bank street and number'), max_length=255, blank=True, null=True, default=None) bank_zip = models.CharField(_(u'bank ZIP'), max_length=255, blank=True, null=True, default=None) bank_city = models.CharField(_(u'bank city'), max_length=255, blank=True, null=True, default=None) bank_country = CountryField(_(u'bank country'), max_length=255, blank=True, null=True, default=None) bank_iban = IBANField(verbose_name=_(u'Account number (IBAN)'), default=None) bank_swift_bic = SWIFTBICField(verbose_name=_(u'Bank SWIFT / BIC'), default=None) # Issuer details supplier_name = models.CharField(_(u'supplier name'), max_length=255, default=None) supplier_street = models.CharField(_(u'supplier street and number'), max_length=255, blank=True, null=True, default=None) supplier_zip = models.CharField(_(u'supplier ZIP'), max_length=255, blank=True, null=True, default=None) supplier_city = models.CharField(_(u'supplier city'), max_length=255, blank=True, null=True, default=None) supplier_country = CountryField(_(u'supplier country'), default=None) supplier_registration_id = models.CharField(_(u'supplier Reg. No.'), max_length=255, blank=True, null=True, default=None) supplier_tax_id = models.CharField(_(u'supplier Tax No.'), max_length=255, blank=True, null=True, default=None) supplier_vat_id = VATField(_(u'supplier VAT No.'), blank=True, null=True, default=None) supplier_additional_info = JSONField( _(u'supplier additional information'), load_kwargs={'object_pairs_hook': OrderedDict}, blank=True, null=True, default=None) # for example www or legal matters # Contact details issuer_name = models.CharField(_(u'issuer name'), max_length=255, blank=True, null=True, default=None) issuer_email = models.EmailField(_(u'issuer email'), blank=True, null=True, default=None) issuer_phone = models.CharField(_(u'issuer phone'), max_length=255, blank=True, null=True, default=None) # Customer details customer_name = models.CharField(_(u'customer name'), max_length=255) customer_street = models.CharField(_(u'customer street and number'), max_length=255, blank=True, null=True, default=None) customer_zip = models.CharField(_(u'customer ZIP'), max_length=255, blank=True, null=True, default=None) customer_city = models.CharField(_(u'customer city'), max_length=255, blank=True, null=True, default=None) customer_country = CountryField(_(u'customer country')) customer_registration_id = models.CharField(_(u'customer Reg. No.'), max_length=255, blank=True, null=True, default=None) customer_tax_id = models.CharField(_(u'customer Tax No.'), max_length=255, blank=True, null=True, default=None) customer_vat_id = VATField(_(u'customer VAT No.'), blank=True, null=True, default=None) customer_additional_info = JSONField( _(u'customer additional information'), load_kwargs={'object_pairs_hook': OrderedDict}, blank=True, null=True, default=None) # Shipping details shipping_name = models.CharField(_(u'shipping name'), max_length=255, blank=True, null=True, default=None) shipping_street = models.CharField(_(u'shipping street and number'), max_length=255, blank=True, null=True, default=None) shipping_zip = models.CharField(_(u'shipping ZIP'), max_length=255, blank=True, null=True, default=None) shipping_city = models.CharField(_(u'shipping city'), max_length=255, blank=True, null=True, default=None) shipping_country = CountryField(_(u'shipping country'), blank=True, null=True, default=None) # Delivery details delivery_method = models.CharField(_(u'delivery method'), choices=DELIVERY_METHOD, max_length=64, default=DELIVERY_METHOD.PERSONAL_PICKUP) # Other created = models.DateTimeField(_(u'created'), auto_now_add=True) modified = models.DateTimeField(_(u'modified'), auto_now=True) objects = InvoiceManager() class Meta: db_table = 'invoicing_invoices' verbose_name = _(u'invoice') verbose_name_plural = _(u'invoices') ordering = ('date_issue', 'number') def __unicode__(self): return self.full_number def save(self, **kwargs): if self.number in EMPTY_VALUES: self.number = self._get_next_number() if self.full_number in EMPTY_VALUES: self.full_number = self._get_full_number() return super(Invoice, self).save(**kwargs) def get_absolute_url(self): return reverse('invoicing:invoice_detail', args=(self.pk, )) def _get_next_number(self): """ Returnes next invoice number based on ``settings.INVOICING_COUNTER_PERIOD``. .. warning:: This is only used to prepopulate ``number`` field on saving new invoice. To get invoice number always use ``number`` field. .. note:: To get invoice full number use ``full_number`` field. :return: string (generated next number) """ invoice_counter_reset = getattr(settings, 'INVOICING_COUNTER_PERIOD', Invoice.COUNTER_PERIOD.YEARLY) important_date = self.date_tax_point # self.date_issue if invoice_counter_reset == Invoice.COUNTER_PERIOD.DAILY: relative_invoices = Invoice.objects.filter( date_issue=important_date, type=self.type) elif invoice_counter_reset == Invoice.COUNTER_PERIOD.YEARLY: relative_invoices = Invoice.objects.filter( date_issue__year=important_date.year, type=self.type) elif invoice_counter_reset == Invoice.COUNTER_PERIOD.MONTHLY: relative_invoices = Invoice.objects.filter( date_issue__year=important_date.year, date_issue__month=important_date.month, type=self.type) else: raise ImproperlyConfigured( "INVOICING_COUNTER_PERIOD can be set only to these values: DAILY, MONTHLY, YEARLY." ) last_number = relative_invoices.aggregate( Max('number'))['number__max'] or 0 return last_number + 1 def _get_full_number(self): """ Generates on the fly invoice full number from template provided by ``settings.INVOICING_NUMBER_FORMAT``. ``Invoice`` object is provided as ``invoice`` variable to the template, therefore all object fields can be used to generate full number format. .. warning:: This is only used to prepopulate ``full_number`` field on saving new invoice. To get invoice full number always use ``full_number`` field. :return: string (generated full number) """ number_format = getattr( settings, "INVOICING_NUMBER_FORMAT", "{{ invoice.date_tax_point|date:'Y' }}/{{ invoice.number }}") return Template(number_format).render(Context({'invoice': self})) @property def taxation_policy(self): taxation_policy = getattr(settings, 'INVOICING_TAXATION_POLICY', None) if taxation_policy is not None: return import_name(taxation_policy) # Check if supplier is from EU if self.supplier_country: if EUTaxationPolicy.is_in_EU(self.supplier_country.code): return EUTaxationPolicy return None @property def is_overdue(self): return self.date_due < now().date() and self.status not in [ self.STATUS.PAID, self.STATUS.CANCELED ] @property def overdue_days(self): return (now().date() - self.date_due).days @property def payment_term(self): return (self.date_due - self.date_issue).days if self.total > 0 else 0 def set_supplier_data(self, supplier): self.supplier_name = supplier.get('name') self.supplier_street = supplier.get('street', None) self.supplier_zip = supplier.get('zip', None) self.supplier_city = supplier.get('city', None) self.supplier_country = supplier.get('country_code') self.supplier_registration_id = supplier.get('registration_id', None) self.supplier_tax_id = supplier.get('tax_id', None) self.supplier_vat_id = supplier.get('vat_id', None) self.supplier_additional_info = supplier.get('additional_info', None) bank = supplier.get('bank') self.bank_name = bank.get('name') self.bank_street = bank.get('street') self.bank_zip = bank.get('zip') self.bank_city = bank.get('city') self.bank_country = bank.get('country_code') self.bank_iban = bank.get('iban') self.bank_swift_bic = bank.get('swift_bic') def set_customer_data(self, customer): self.customer_name = customer.get('name') self.customer_street = customer.get('street', None) self.customer_zip = customer.get('zip', None) self.customer_city = customer.get('city', None) self.customer_country = customer.get('country_code') self.customer_registration_id = customer.get('registration_id', None) self.customer_tax_id = customer.get('tax_id', None) self.customer_vat_id = customer.get('vat_id', None) self.customer_additional_info = customer.get('additional_info', None) def set_shipping_data(self, shipping): self.shipping_name = shipping.get('name', None) self.shipping_street = shipping.get('street', None) self.shipping_zip = shipping.get('zip', None) self.shipping_city = shipping.get('city', None) self.shipping_country = shipping.get('country_code', None) # http://www.superfaktura.sk/blog/neplatca-dph-vzor-faktury/ def is_supplier_vat_id_visible(self): if self.vat is None and self.supplier_country == self.customer_country: return False # VAT is not 0 if self.vat != 0 or self.item_set.filter(tax_rate__gt=0).exists(): return True # VAT is 0, check if customer is from EU and from same country as supplier is_EU_customer = EUTaxationPolicy.is_in_EU( self.customer_country.code) if self.customer_country else False return is_EU_customer and self.supplier_country != self.customer_country @property def vat_summary(self): #rates_and_sum = self.item_set.all().annotate(base=Sum(F('qty')*F('price_per_unit'))).values('tax_rate', 'base') #rates_and_sum = self.item_set.all().values('tax_rate').annotate(Sum('price_per_unit')) #rates_and_sum = self.item_set.all().values('tax_rate').annotate(Sum(F('qty')*F('price_per_unit'))) from django.db import connection cursor = connection.cursor() cursor.execute( 'select tax_rate as rate, SUM(quantity*unit_price) as base, ROUND(CAST(SUM(quantity*unit_price*(tax_rate/100)) AS numeric), 2) as vat from invoicing_items where invoice_id = %s group by tax_rate;', [self.pk]) desc = cursor.description return [ dict(zip([col[0] for col in desc], row)) for row in cursor.fetchall() ] @property def subtotal(self): sum = 0 for item in self.item_set.all(): sum += item.subtotal return round(sum, 2) @property def vat(self): if len(self.vat_summary) == 1 and self.vat_summary[0]['vat'] is None: return None vat = 0 for vat_rate in self.vat_summary: vat += vat_rate['vat'] or 0 return vat @property def discount_value(self): total = self.subtotal + self.vat or 0 # subtotal with vat discount_value = total * (Decimal(self.discount) / 100 ) # subtract discount amount return round(discount_value, 2) @property def total(self): #total = self.subtotal + self.vat # subtotal with vat total = 0 for vat_rate in self.vat_summary: total += float(vat_rate['vat'] or 0) + float(vat_rate['base']) total *= float( (100 - float(self.discount)) / 100) # subtract discount amount total -= float(self.credit) # subtract credit #total -= self.already_paid # subtract already paid return round(total, 2)
class Collection(models.Model): name = models.CharField(max_length=200, unique=True, null=False, verbose_name="Name of collection") DOI = models.CharField( max_length=200, unique=True, blank=True, null=True, default=None, verbose_name= "DOI of the corresponding paper (required if you want your maps to be archived in Stanford Digital Repository)" ) authors = models.CharField(max_length=5000, blank=True, null=True) paper_url = models.CharField(max_length=200, blank=True, null=True) journal_name = models.CharField(max_length=200, blank=True, null=True, default=None) description = models.TextField(blank=True, null=True) full_dataset_url = models.URLField( max_length=200, blank=True, null=True, verbose_name="Full dataset URL", help_text= "Link to an external dataset the maps in this collection have been generated from (for example: \"https://openfmri.org/dataset/ds000001\" or \"http://dx.doi.org/10.15387/fcp_indi.corr.mpg1\")" ) owner = models.ForeignKey(User) contributors = models.ManyToManyField( User, related_name="collection_contributors", related_query_name="contributor", blank=True, help_text= "Select other NeuroVault users to add as contributes to the collection. Contributors can add, edit and delete images in the collection.", verbose_name="Contributors") private = models.BooleanField(choices=(( False, 'Public (The collection will be accessible by anyone and all the data in it will be distributed under CC0 license)' ), (True, 'Private (The collection will be not listed in the NeuroVault index. It will be possible to shared it with others at a private URL.)' )), default=False, verbose_name="Accesibility") private_token = models.CharField(max_length=8, blank=True, null=True, unique=True, db_index=True, default=None) add_date = models.DateTimeField('date published', auto_now_add=True) modify_date = models.DateTimeField('date modified', auto_now=True) doi_add_date = models.DateTimeField('date the DOI was added', editable=False, blank=True, null=True, db_index=True) type_of_design = models.CharField( choices=[('blocked', 'blocked'), ('eventrelated', 'event_related'), ('hybridblockevent', 'hybrid block/event'), ('other', 'other')], max_length=200, blank=True, help_text="Blocked, event-related, hybrid, or other", null=True, verbose_name="Type of design") number_of_imaging_runs = models.IntegerField( help_text="Number of imaging runs acquired", null=True, verbose_name="No. of imaging runs", blank=True) number_of_experimental_units = models.IntegerField( help_text= "Number of blocks, trials or experimental units per imaging run", null=True, verbose_name="No. of experimental units", blank=True) length_of_runs = models.FloatField( help_text="Length of each imaging run in seconds", null=True, verbose_name="Length of runs", blank=True) length_of_blocks = models.FloatField( help_text="For blocked designs, length of blocks in seconds", null=True, verbose_name="Length of blocks", blank=True) length_of_trials = models.CharField( help_text= "Length of individual trials in seconds. If length varies across trials, enter 'variable'. ", max_length=200, null=True, verbose_name="Length of trials", blank=True) optimization = models.NullBooleanField( help_text="Was the design optimized for efficiency", null=True, verbose_name="Optimization?", blank=True) optimization_method = models.CharField( help_text="What method was used for optimization?", verbose_name="Optimization method", max_length=200, null=True, blank=True) subject_age_mean = models.FloatField(help_text="Mean age of subjects", null=True, verbose_name="Subject age mean", blank=True) subject_age_min = models.FloatField(help_text="Minimum age of subjects", null=True, verbose_name="Subject age min", blank=True) subject_age_max = models.FloatField(help_text="Maximum age of subjects", null=True, verbose_name="Subject age max", blank=True) handedness = models.CharField(choices=[('right', 'right'), ('left', 'left'), ('both', 'both')], max_length=200, blank=True, help_text="Handedness of subjects", null=True, verbose_name="Handedness") proportion_male_subjects = models.FloatField( validators=[MinValueValidator(0.0), MaxValueValidator(1.0)], help_text="The proportion (not percentage) of subjects who were male", null=True, verbose_name="Prop. male subjects", blank=True) inclusion_exclusion_criteria = models.CharField( help_text= "Additional inclusion/exclusion criteria, if any (including specific sampling strategies that limit inclusion to a specific group, such as laboratory members)", verbose_name="Inclusion / exclusion criteria", max_length=200, null=True, blank=True) number_of_rejected_subjects = models.IntegerField( help_text="Number of subjects scanned but rejected from analysis", null=True, verbose_name="No. of rejected subjects", blank=True) group_comparison = models.NullBooleanField( help_text="Was this study a comparison between subject groups?", null=True, verbose_name="Group comparison?", blank=True) group_description = models.CharField( help_text="A description of the groups being compared", verbose_name="Group description", max_length=200, null=True, blank=True) scanner_make = models.CharField(help_text="Manufacturer of MRI scanner", verbose_name="Scanner make", max_length=200, null=True, blank=True) scanner_model = models.CharField(help_text="Model of MRI scanner", verbose_name="Scanner model", max_length=200, null=True, blank=True) field_strength = models.FloatField( help_text="Field strength of MRI scanner (in Tesla)", null=True, verbose_name="Field strength", blank=True) pulse_sequence = models.CharField( help_text="Description of pulse sequence used for fMRI", verbose_name="Pulse sequence", max_length=200, null=True, blank=True) parallel_imaging = models.CharField( help_text="Description of parallel imaging method and parameters", verbose_name="Parallel imaging", max_length=200, null=True, blank=True) field_of_view = models.FloatField( help_text="Imaging field of view in millimeters", null=True, verbose_name="Field of view", blank=True) matrix_size = models.IntegerField( help_text="Matrix size for MRI acquisition", null=True, verbose_name="Matrix size", blank=True) slice_thickness = models.FloatField( help_text= "Distance between slices (includes skip or distance factor) in millimeters", null=True, verbose_name="Slice thickness", blank=True) skip_distance = models.FloatField( help_text="The size of the skipped area between slices in millimeters", null=True, verbose_name="Skip distance", blank=True) acquisition_orientation = models.CharField( help_text="The orientation of slices", verbose_name="Acquisition orientation", max_length=200, null=True, blank=True) order_of_acquisition = models.CharField( choices=[('ascending', 'ascending'), ('descending', 'descending'), ('interleaved', 'interleaved')], max_length=200, blank=True, help_text= "Order of acquisition of slices (ascending, descending, or interleaved)", null=True, verbose_name="Order of acquisition") repetition_time = models.FloatField( help_text="Repetition time (TR) in milliseconds", null=True, verbose_name="Repetition time", blank=True) echo_time = models.FloatField(help_text="Echo time (TE) in milliseconds", null=True, verbose_name="Echo time", blank=True) flip_angle = models.FloatField(help_text="Flip angle in degrees", null=True, verbose_name="Flip angle", blank=True) software_package = models.CharField( help_text= "If a single software package was used for all analyses, specify that here", verbose_name="Software package", max_length=200, null=True, blank=True) software_version = models.CharField( help_text="Version of software package used", verbose_name="Software version", max_length=200, null=True, blank=True) order_of_preprocessing_operations = models.CharField( help_text="Specify order of preprocessing operations", verbose_name="Order of preprocessing", max_length=200, null=True, blank=True) quality_control = models.CharField( help_text="Describe quality control measures", verbose_name="Quality control", max_length=200, null=True, blank=True) used_b0_unwarping = models.NullBooleanField( help_text="Was B0 distortion correction used?", null=True, verbose_name="Used B0 unwarping?", blank=True) b0_unwarping_software = models.CharField( help_text= "Specify software used for distortion correction if different from the main package", verbose_name="B0 unwarping software", max_length=200, null=True, blank=True) used_slice_timing_correction = models.NullBooleanField( help_text="Was slice timing correction used?", null=True, verbose_name="Slice timing correction?", blank=True) slice_timing_correction_software = models.CharField( help_text= "Specify software used for slice timing correction if different from the main package", verbose_name="Slice timing correction software", max_length=200, null=True, blank=True) used_motion_correction = models.NullBooleanField( help_text="Was motion correction used?", null=True, verbose_name="Motion correction?", blank=True) motion_correction_software = models.CharField( help_text= "Specify software used for motion correction if different from the main package", verbose_name="Motion correction software", max_length=200, null=True, blank=True) motion_correction_reference = models.CharField( help_text="Reference scan used for motion correction", verbose_name="Motion correction reference", max_length=200, null=True, blank=True) motion_correction_metric = models.CharField( help_text="Similarity metric used for motion correction", verbose_name="Motion correction metric", max_length=200, null=True, blank=True) motion_correction_interpolation = models.CharField( help_text="Interpolation method used for motion correction", verbose_name="Motion correction interpolation", max_length=200, null=True, blank=True) used_motion_susceptibiity_correction = models.NullBooleanField( help_text="Was motion-susceptibility correction used?", null=True, verbose_name="Motion susceptibility correction?", blank=True) used_intersubject_registration = models.NullBooleanField( help_text="Were subjects registered to a common stereotactic space?", null=True, verbose_name="Intersubject registration?", blank=True) intersubject_registration_software = models.CharField( help_text= "Specify software used for intersubject registration if different from main package", verbose_name="Registration software", max_length=200, null=True, blank=True) intersubject_transformation_type = models.CharField( choices=[('linear', 'linear'), ('nonlinear', 'nonlinear')], max_length=200, blank=True, help_text="Was linear or nonlinear registration used?", null=True, verbose_name="Intersubject transformation type") nonlinear_transform_type = models.CharField( help_text= "If nonlinear registration was used, describe transform method", verbose_name="Nonlinear transform type", max_length=200, null=True, blank=True) transform_similarity_metric = models.CharField( help_text="Similarity metric used for intersubject registration", verbose_name="Transform similarity metric", max_length=200, null=True, blank=True) interpolation_method = models.CharField( help_text="Interpolation method used for intersubject registration", verbose_name="Interpolation method", max_length=200, null=True, blank=True) object_image_type = models.CharField( help_text= "What type of image was used to determine the transformation to the atlas? (e.g. T1, T2, EPI)", verbose_name="Object image type", max_length=200, null=True, blank=True) functional_coregistered_to_structural = models.NullBooleanField( help_text= "Were the functional images coregistered to the subject's structural image?", null=True, verbose_name="Coregistered to structural?", blank=True) functional_coregistration_method = models.CharField( help_text="Method used to coregister functional to structural images", verbose_name="Coregistration method", max_length=200, null=True, blank=True) coordinate_space = models.CharField( choices=[('mni', 'MNI'), ('talairach', 'Talairach'), ('mni2tal', 'MNI2Tal'), ('other', 'other')], max_length=200, blank=True, help_text="Name of coordinate space for registration target", null=True, verbose_name="Coordinate space") target_template_image = models.CharField( help_text="Name of target template image", verbose_name="Target template image", max_length=200, null=True, blank=True) target_resolution = models.FloatField( help_text="Voxel size of target template in millimeters", null=True, verbose_name="Target resolution", blank=True) used_smoothing = models.NullBooleanField( help_text="Was spatial smoothing applied?", null=True, verbose_name="Used smoothing?", blank=True) smoothing_type = models.CharField( help_text="Describe the type of smoothing applied", verbose_name="Type of smoothing", max_length=200, null=True, blank=True) smoothing_fwhm = models.FloatField( help_text= "The full-width at half-maximum of the smoothing kernel in millimeters", null=True, verbose_name="Smoothing FWHM", blank=True) resampled_voxel_size = models.FloatField( help_text="Voxel size in mm of the resampled, atlas-space images", null=True, verbose_name="Resampled voxel size", blank=True) intrasubject_model_type = models.CharField( help_text="Type of model used (e.g., regression)", verbose_name="Model type", max_length=200, null=True, blank=True) intrasubject_estimation_type = models.CharField( help_text= "Estimation method used for model (e.g., OLS, generalized least squares)", verbose_name="Estimation type", max_length=200, null=True, blank=True) intrasubject_modeling_software = models.CharField( help_text= "Software used for intrasubject modeling if different from overall package", verbose_name="Modeling software", max_length=200, null=True, blank=True) hemodynamic_response_function = models.CharField( help_text="Nature of HRF model", verbose_name="Hemodynamic response function", max_length=200, null=True, blank=True) used_temporal_derivatives = models.NullBooleanField( help_text="Were temporal derivatives included?", null=True, verbose_name="Temporal derivatives?", blank=True) used_dispersion_derivatives = models.NullBooleanField( help_text="Were dispersion derivatives included?", null=True, verbose_name="Dispersion derivatives?", blank=True) used_motion_regressors = models.NullBooleanField( help_text="Were motion regressors included?", null=True, verbose_name="Motion regressors?", blank=True) used_reaction_time_regressor = models.NullBooleanField( help_text="Was a reaction time regressor included?", null=True, verbose_name="Reaction time regressor?", blank=True) used_orthogonalization = models.NullBooleanField( help_text= "Were any regressors specifically orthogonalized with respect to others?", null=True, verbose_name="Orthogonalization?", blank=True) orthogonalization_description = models.CharField( help_text="If orthogonalization was used, describe here", verbose_name="Orthogonalization description", max_length=200, null=True, blank=True) used_high_pass_filter = models.NullBooleanField( help_text="Was high pass filtering applied?", null=True, verbose_name="High-pass filter?", blank=True) high_pass_filter_method = models.CharField( help_text="Describe method used for high pass filtering", verbose_name="High-pass filtering method", max_length=200, null=True, blank=True) autocorrelation_model = models.CharField( help_text= "What autocorrelation model was used (or 'none' of none was used)", verbose_name="Autocorrelation method", max_length=200, null=True, blank=True) group_model_type = models.CharField( help_text="Type of group model used (e.g., regression)", verbose_name="Group model type", max_length=200, null=True, blank=True) group_estimation_type = models.CharField( help_text= "Estimation method used for group model (e.g., OLS, generalized least squares)", verbose_name="Group estimation type", max_length=200, null=True, blank=True) group_modeling_software = models.CharField( help_text= "Software used for group modeling if different from overall package", verbose_name="Group modeling software", max_length=200, null=True, blank=True) group_inference_type = models.CharField( choices=[('randommixedeffects', 'random/mixed effects'), ('fixedeffects', 'fixed effects')], max_length=200, blank=True, help_text="Type of inference for group model", null=True, verbose_name="Group inference type") group_model_multilevel = models.CharField( help_text= "If more than 2-levels, describe the levels and assumptions of the model (e.g. are variances assumed equal between groups)", verbose_name="Multilevel modeling", max_length=200, null=True, blank=True) group_repeated_measures = models.NullBooleanField( help_text="Was this a repeated measures design at the group level?", null=True, verbose_name="Repeated measures", blank=True) group_repeated_measures_method = models.CharField( help_text= "If multiple measurements per subject, list method to account for within subject correlation, exact assumptions made about correlation/variance", verbose_name="Repeated measures method", max_length=200, null=True, blank=True) @property def is_statisticmap_set(self): return all((isinstance(i, StatisticMap) for i in self.basecollectionitem_set.all())) def get_absolute_url(self): return_cid = self.id if self.private: return_cid = self.private_token return reverse('collection_details', args=[str(return_cid)]) def __unicode__(self): return self.name def save(self, *args, **kwargs): if self.DOI is not None and self.DOI.strip() == "": self.DOI = None if self.private_token is not None and self.private_token.strip() == "": self.private_token = None if self.DOI and not self.private and not self.doi_add_date: self.doi_add_date = datetime.now() # run calculations when collection turns public privacy_changed = False DOI_changed = False if self.pk is not None: old_object = Collection.objects.get(pk=self.pk) old_is_private = old_object.private old_has_DOI = old_object.DOI is not None privacy_changed = old_is_private != self.private DOI_changed = old_has_DOI != (self.DOI is not None) super(Collection, self).save(*args, **kwargs) if (privacy_changed and not self.private) or (DOI_changed and self.DOI is not None): for image in self.basecollectionitem_set.instance_of(Image).all(): if image.pk: generate_glassbrain_image.apply_async([image.pk]) run_voxelwise_pearson_similarity.apply_async([image.pk]) class Meta: app_label = 'statmaps' def delete(self, using=None): cid = self.pk for image in self.basecollectionitem_set.instance_of(Image): image.delete() for nidmresult in self.basecollectionitem_set.instance_of(NIDMResults): nidmresult.delete() ret = super(Collection, self).delete(using=using) collDir = os.path.join(PRIVATE_MEDIA_ROOT, 'images', str(cid)) try: shutil.rmtree(collDir) except OSError: print 'Image directory for collection %s does not exist' % cid return ret
class Ticket(models.Model): # TODO - deprecate, replace with ticket_type.training training = models.ForeignKey( Training, verbose_name='Тренинг', on_delete=models.PROTECT, related_name='tickets', ) email = models.EmailField() first_name = models.CharField('Имя', max_length=255, blank=True) last_name = models.CharField('Фамилия', max_length=255, blank=True) # deprecated registration_date = models.DateField( 'Дата регистрации', auto_now_add=True, null=True ) created = models.DateTimeField('Дата создания', auto_now_add=True) status = models.CharField( 'Статус', max_length=40, default='normal', choices=( ('normal', 'Участник'), ('canceled', 'Отказ'), # отказ, перенос, замена, неявка ), ) ticket_class = models.CharField( 'Тип билета', max_length=40, default='normal', choices=( ('normal', 'Обычный'), ('stipend', 'Стипендия'), ('staff', 'Стафф'), ('replacement', 'Замена (заменяет другого участника)'), ('carry-over', 'Перенос (с прошлого мероприятия)'), ('free-repeat', 'Бесплатный повтор'), ), ) ticket_type = models.ForeignKey( 'ratio.TicketType', null=True, blank=True, on_delete=models.PROTECT, ) payment_amount = models.IntegerField( 'Размер оплаты', validators=[MinValueValidator(0), MaxValueValidator(1000000)], ) comment = models.TextField(blank=True) notion_link = models.URLField(blank=True) objects = TicketManager() class Meta: verbose_name = 'Участник' verbose_name_plural = 'Участники' unique_together = [['training', 'email']] ordering = ['-created'] def __str__(self): return f'{self.training} - {self.email}' def clean(self): if self.ticket_type and self.ticket_type.training.pk != self.training.pk: raise ValidationError( {'ticket_type': ['Тип билета должен соответствовать тренингу']} ) def save(self, *args, **kwargs): created = not bool(self.pk) super().save(*args, **kwargs) if created: from ..channels import send_new_ticket_email logger.info('scheduling new_ticket email on commit') transaction.on_commit(lambda: send_new_ticket_email(self)) def set_notion_link(self, link: str): if self.notion_link: raise ValidationError({'notion_link': ['Ссылка на Notion уже заполнена']}) if not self.training.notion_created_email: raise ValidationError( {'notion_link': ['Для этого тренинга не нужны Notion-ссылки в билетах']} ) self.notion_link = link self.full_clean() self.training.send_notion_created_email(self) self.save() def replace_notion_link(self, link: str, send_email: bool): if not self.notion_link: raise ValidationError( {'notion_link': ['Ссылка на Notion ещё не заполнена']} ) if not self.training.notion_created_email: raise ValidationError( {'notion_link': ['Для этого тренинга не нужны Notion-ссылки в билетах']} ) self.notion_link = link self.full_clean() if send_email: self.training.send_notion_created_email(self) self.save() # UID is used for sharing anonymised data with third-party, # e.g. with academy crowd when we collect data from rationality tests. def uid(self): SALT = settings.KOCHERGA_MAILCHIMP_UID_SALT.encode() return hashlib.sha1(SALT + self.email.lower().encode()).hexdigest()[:10]