Esempio n. 1
0
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()
Esempio n. 2
0
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()
Esempio n. 3
0
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
Esempio n. 4
0
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)
Esempio n. 5
0
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)))
Esempio n. 6
0
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)]
Esempio n. 7
0
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
Esempio n. 8
0
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)
Esempio n. 9
0
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,
        )
Esempio n. 10
0
class Customer(models.Model):
    name = models.CharField(max_length=60)
    email = models.EmailField()
    age = models.PositiveIntegerField(
        validators=[MinValueValidator(16),
                    MaxValueValidator(120)])
Esempio n. 11
0
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)
Esempio n. 12
0
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)])
Esempio n. 14
0
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
Esempio n. 16
0
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)])
Esempio n. 17
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)
Esempio n. 18
0
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)])
Esempio n. 19
0
class DecimalFieldModel(models.Model):
    decimal_field = models.DecimalField(
        max_digits=3,
        decimal_places=1,
        validators=[MinValueValidator(1),
                    MaxValueValidator(3)])
Esempio n. 20
0
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)
Esempio n. 21
0
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
Esempio n. 22
0
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'
Esempio n. 23
0
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) + ")")
Esempio n. 24
0
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)])
Esempio n. 25
0
def max_value_current_year(value):
    return MaxValueValidator(current_year())(value)
Esempio n. 26
0
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)
Esempio n. 27
0
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)
Esempio n. 28
0
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)
Esempio n. 29
0
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
Esempio n. 30
0
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]