class Scanner(models.Model): """A scanner, i.e. a template for actual scanning jobs.""" objects = InheritanceManager() name = models.CharField(max_length=256, unique=True, null=False, verbose_name='Navn') organization = models.ForeignKey(Organization, null=False, verbose_name='Organisation') group = models.ForeignKey(Group, null=True, blank=True, verbose_name='Gruppe') schedule = RecurrenceField(max_length=1024, verbose_name='Planlagt afvikling') do_cpr_scan = models.BooleanField(default=True, verbose_name='CPR') do_name_scan = models.BooleanField(default=False, verbose_name='Navn') do_address_scan = models.BooleanField(default=False, verbose_name='Adresse') do_ocr = models.BooleanField(default=False, verbose_name='Scan billeder') do_last_modified_check = models.BooleanField( default=True, verbose_name='Tjek sidst ændret dato') do_cpr_modulus11 = models.BooleanField(default=True, verbose_name='Tjek modulus-11') do_cpr_ignore_irrelevant = models.BooleanField( default=True, verbose_name='Ignorer ugyldige fødselsdatoer') columns = models.CommaSeparatedIntegerField(max_length=128, null=True, blank=True) regex_rules = models.ManyToManyField(RegexRule, blank=True, verbose_name='Regex regler') recipients = models.ManyToManyField(UserProfile, blank=True, verbose_name='Modtagere') # Spreadsheet annotation and replacement parameters # Save a copy of any spreadsheets scanned with annotations # in each row where matches were found. If this is enabled and any of # the replacement parameters are enabled (e.g. do_cpr_replace), matches # will also be replaced with the specified text (e.g. cpr_replace_text). output_spreadsheet_file = models.BooleanField(default=False) # Replace CPRs? do_cpr_replace = models.BooleanField(default=False) # Text to replace CPRs with cpr_replace_text = models.CharField(max_length=2048, null=True, blank=True) # Replace names? do_name_replace = models.BooleanField(default=False) # Text to replace names with name_replace_text = models.CharField(max_length=2048, null=True, blank=True) # Replace addresses? do_address_replace = models.BooleanField(default=False) # Text to replace addresses with address_replace_text = models.CharField(max_length=2048, null=True, blank=True) is_running = models.BooleanField(default=False) @property def schedule_description(self): """A lambda for creating schedule description strings.""" rules = [r for r in self.schedule.rrules] # Use r.to_text() to render dates = [d for d in self.schedule.rdates] if len(rules) > 0 or len(dates) > 0: return u"Ja" else: return u"Nej" # Run error messages ALREADY_RUNNING = ("Scanneren kunne ikke startes," + " fordi der allerede er en scanning i gang for den.") NO_VALID_DOMAINS = ("Scanneren kunne ikke startes," + " fordi den ikke har nogen gyldige domæner.") # DON'T USE DIRECTLY !!! # Use process_urls property instead. encoded_process_urls = models.CharField(max_length=262144, null=True, blank=True) # Booleans for control of scanners run from web service. do_run_synchronously = models.BooleanField(default=False) is_visible = models.BooleanField(default=True) def _get_process_urls(self): s = self.encoded_process_urls if s: urls = json.loads(s) else: urls = [] return urls def _set_process_urls(self, urls): self.encoded_process_urls = json.dumps(urls) process_urls = property(_get_process_urls, _set_process_urls) # First possible start time FIRST_START_TIME = datetime.time(18, 0) # Amount of quarter-hours that can be added to the start time STARTTIME_QUARTERS = 6 * 4 def get_start_time(self): """The time of day the Scanner should be automatically started.""" added_minutes = 15 * (self.pk % Scanner.STARTTIME_QUARTERS) added_hours = int(added_minutes / 60) added_minutes -= added_hours * 60 return Scanner.FIRST_START_TIME.replace( hour=Scanner.FIRST_START_TIME.hour + added_hours, minute=Scanner.FIRST_START_TIME.minute + added_minutes) @property def has_valid_domains(self): return len([d for d in self.domains.all() if d.validation_status]) > 0 @classmethod def modulo_for_starttime(cls, time): """Convert a datetime.time object to the corresponding modulo value. The modulo value can be used to search the database for scanners that should be started at the given time by filtering a query with: (WebScanner.pk % WebScanner.STARTTIME_QUARTERS) == <modulo_value> """ if (time < cls.FIRST_START_TIME): return None hours = time.hour - cls.FIRST_START_TIME.hour minutes = 60 * hours + time.minute - cls.FIRST_START_TIME.minute return int(minutes / 15) @property def display_name(self): """The name used when displaying the scanner on the web page.""" return "WebScanner '%s'" % self.name def __unicode__(self): """Return the name of the scanner.""" return self.name def __str__(self): """Return the name of the scanner.""" return self.__unicode__() def run(self, test_only=False, blocking=False, user=None): """Run a scan with the Scanner. Return the Scan object if we started the scanner. Return None if there is already a scanner running, or if there was a problem running the scanner. If test_only is True, only check if we can run a scan, don't actually run one. """ if self.is_running: return Scanner.ALREADY_RUNNING if not self.has_valid_domains: return Scanner.NO_VALID_DOMAINS # Create a new Scan scan = self.create_scan() if isinstance(scan, str): return scan # Add user as recipient on scan if user: scan.recipients.add(user.profile) # Get path to run script SCRAPY_WEBSCANNER_DIR = os.path.join(settings.PROJECT_DIR, "scrapy-webscanner") if test_only: return scan if not os.path.exists(scan.scan_log_dir): os.makedirs(scan.scan_log_dir) log_file = open(scan.scan_log_file, "a") if not os.path.exists(scan.scan_output_files_dir): os.makedirs(scan.scan_output_files_dir) try: process = Popen( [os.path.join(SCRAPY_WEBSCANNER_DIR, "run.sh"), str(scan.pk)], cwd=SCRAPY_WEBSCANNER_DIR, stderr=log_file, stdout=log_file) if blocking: process.communicate() except Exception as e: print(e) return None return scan class Meta: abstract = False ordering = ['name'] db_table = 'os2webscanner_scanner'
class VersionDiff(models.Model): objects = InheritanceManager() num_added = models.IntegerField() num_modified = models.IntegerField() num_removed = models.IntegerField() num_unchanged = models.IntegerField() COLS_FROM_TO = [] COLS_CHANGE_COUNT = [] @property def total(self): return self.num_added + self.num_modified + self.num_removed + self.num_unchanged @property def percent_added(self): return 100.0 * self.num_added / self.total @property def percent_modified(self): return 100.0 * self.num_modified / self.total @property def percent_removed(self): return 100.0 * self.num_removed / self.total @property def percent_unchanged(self): return 100.0 * self.num_unchanged / self.total @staticmethod def description(version_from, version_to): from_date = version_from.annotation_date.date() to_date = version_to.annotation_date.date() time_between = timesince(version_to.annotation_date, version_from.annotation_date) return f"{from_date} to {to_date} ({time_between})" def get_diff_sql(self, _select_columns): return NotImplementedError() def create_version_diffs(self): vg_column_prefix = self.VG_COLUMN_PREFIX cols_from_to = self.COLS_FROM_TO cols_change_count = self.COLS_CHANGE_COUNT columns = cols_from_to + cols_change_count ALIASES = ["v1", "v2"] def get_column_alias(alias, column): return f'"{alias}"."{column}"' alias_columns = defaultdict(list) counters_from_to = [] counters_change = [] for c in columns: for alias in ALIASES: column = get_column_alias(alias, c) alias_columns[alias].append(column) variant_column = f"{vg_column_prefix}__{c}" vgc = VariantGridColumn.objects.get(variant_column=variant_column) if c in cols_from_to: counters_from_to.append((c, vgc, defaultdict(Counter))) elif c in cols_change_count: # In a list so that we can alter the object rather than copies counters_change.append((c, vgc, [0])) select_columns = [] for alias in ALIASES: # Always add primary key select_columns.append(get_column_alias(alias, "id")) for ac in alias_columns.values(): select_columns.extend(ac) sql = self.get_diff_sql(select_columns) cursor = connection.cursor() cursor.execute(sql) self.num_added = 0 self.num_modified = 0 self.num_removed = 0 self.num_unchanged = 0 for data in dictfetchall(cursor, column_names=select_columns): v1_id, v2_id = [data[k] for k in select_columns[:2]] # Could be modified a1 = ALIASES[0] a2 = ALIASES[1] v1_columns = alias_columns[a1] v2_columns = alias_columns[a2] v1 = [data[v] for v in v1_columns] v2 = [data[v] for v in v2_columns] modified = reduce(operator.or_, [a != b for a, b in zip(v1, v2)]) if v1_id and not v2_id: self.num_removed += 1 elif v2_id and not v1_id: self.num_added += 1 else: if modified: self.num_modified += 1 else: self.num_unchanged += 1 for column, vgc, counters in counters_from_to: val_1 = data[get_column_alias(a1, column)] val_2 = data[get_column_alias(a2, column)] counters[val_1][val_2] += 1 for column, vgc, count_list in counters_change: val_1 = data[get_column_alias(a1, column)] val_2 = data[get_column_alias(a2, column)] if val_1 != val_2: count_list[0] += 1 self.save() for column, vgc, count_list in counters_change: vdr = VersionDiffChangeCountResult(version_diff=self, vg_column=vgc, count=count_list[0]) vdr.save() for column, vgc, counters in counters_from_to: for value_from, counter in counters.items(): for value_to, count in counter.items(): vdr = VersionDiffFromToResult(version_diff=self, vg_column=vgc, value_from=value_from, value_to=value_to, count=count) vdr.save() def get_vg_column(self, base_column): variant_column = f"{self.VG_COLUMN_PREFIX}__{base_column}" return VariantGridColumn.objects.get(variant_column=variant_column) def get_diff_results(self): results = defaultdict(list) for c in self.COLS_FROM_TO: variant_column = self.get_vg_column(c) df = self.get_column_version_diff_as_df(variant_column) results["cols_from_to"].append((variant_column, df)) for c in self.COLS_CHANGE_COUNT: variant_column = self.get_vg_column(c) count = self.get_column_version_diff_change_count(variant_column) results["cols_change_count"].append((variant_column, count)) return results def get_column_version_diff_as_df(self, variant_grid_column): qs = self.versiondifffromtoresult_set.filter( vg_column=variant_grid_column) data = defaultdict(dict) for value_from, value_to, count in qs.values_list( 'value_from', 'value_to', 'count'): data[value_from][value_to] = count df = pd.DataFrame.from_dict(data).fillna(0) NO_ENTRY_DICT = {None: "No Entry"} return df.rename(index=NO_ENTRY_DICT, columns=NO_ENTRY_DICT) def get_column_version_diff_change_count(self, variant_grid_column): vd = self.versiondiffchangecountresult_set.get( vg_column=variant_grid_column) return vd.count
class Bookmark(models.Model): user = models.ForeignKey(get_user_model(), related_name='bookmark_contents') created = models.DateTimeField(default=timezone.now) objects = InheritanceManager()
class BuggyModel(models.Model): objects = InheritanceManager() name = models.CharField()
class FlagBase(models.Model): ''' This defines a condition which allows products or categories to be made visible, or be prevented from being visible. Attributes: description (str): A human-readable description that is used to identify the flag to staff in the admin interface. It's not seen anywhere else in Registrasion. condition (int): This determines the effect of this flag's condition being met. There are two types of condition: ``ENABLE_IF_TRUE`` conditions switch on the products and categories included under this flag if *any* such condition is met. ``DISABLE_IF_FALSE`` conditions *switch off* the products and categories included under this flag is any such condition *is not* met. If you have both types of conditions attached to a Product, every ``DISABLE_IF_FALSE`` condition must be met, along with one ``ENABLE_IF_TRUE`` condition. products ([inventory.Product, ...]): The Products affected by this flag. categories ([inventory.Category, ...]): The Categories whose Products are affected by this flag. ''' objects = InheritanceManager() DISABLE_IF_FALSE = 1 ENABLE_IF_TRUE = 2 def __str__(self): return self.description def effects(self): ''' Returns all of the items affected by this condition. ''' return itertools.chain(self.products.all(), self.categories.all()) @property def is_disable_if_false(self): return self.condition == FlagBase.DISABLE_IF_FALSE @property def is_enable_if_true(self): return self.condition == FlagBase.ENABLE_IF_TRUE description = models.CharField(max_length=255) condition = models.IntegerField( default=ENABLE_IF_TRUE, choices=( (DISABLE_IF_FALSE, _("Disable if false")), (ENABLE_IF_TRUE, _("Enable if true")), ), help_text=_("If there is at least one 'disable if false' flag " "defined on a product or category, all such flag " " conditions must be met. If there is at least one " "'enable if true' flag, at least one such condition must " "be met. If both types of conditions exist on a product, " "both of these rules apply." ), ) products = models.ManyToManyField( inventory.Product, blank=True, help_text=_("Products affected by this flag's condition."), related_name="flagbase_set", ) categories = models.ManyToManyField( inventory.Category, blank=True, help_text=_("Categories whose products are affected by this flag's " "condition." ), related_name="flagbase_set", )
class Problem(models.Model): """Base class for problems, with common attributes and methods""" __INSERT_SEPARATION = "-- @new data base@" title_md = models.CharField(max_length=100, blank=True) title_html = models.CharField(max_length=200) text_md = models.TextField(max_length=5000, blank=True) text_html = models.TextField(max_length=10000) language = models.CharField(max_length=7, choices=settings.LANGUAGES, default=settings.LANGUAGE_CODE) create_sql = models.TextField(max_length=20000, blank=True) insert_sql = models.TextField(max_length=20000, blank=True) initial_db = JSONField(encoder=DjangoJSONEncoder, default=None, blank=True, null=True) min_stmt = models.PositiveIntegerField(default=1) max_stmt = models.PositiveIntegerField(default=1) collection = models.ForeignKey(Collection, on_delete=models.CASCADE) author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL) creation_date = models.DateTimeField(auto_now_add=True) position = models.PositiveIntegerField(default=1, null=False) # (Dirty) trick to upload ZIP files using the standard admin interface of Django zipfile = models.FileField(upload_to='problem_zips/', default=None, blank=True, null=True) # To query Problem to obtain subclass objects with '.select_subclasses()' objects = InheritanceManager() def clean(self): """Check the number of statements and creates HTML versions from MarkDown""" super().clean() if self.min_stmt > self.max_stmt: raise ValidationError('Invalid statement range', code='invalid_stmt_range') self.title_html = markdown_to_html(self.title_md, remove_initial_p=True) self.text_html = markdown_to_html(self.text_md, remove_initial_p=False) def __str__(self): """String to show in the Admin interface""" return html.fromstring(self.title_html).text_content() def template(self): """Name of the HTML template used to show the problem""" raise NotImplementedError def judge(self, code, executor): """Assess the code with the user solution using a DB executor""" raise NotImplementedError def problem_type(self): """Return an enumeration ProblemType with the problem type""" raise NotImplementedError def solved_by_user(self, user) -> bool: """Whether user has solved the problem or not""" return Submission.objects.filter( user=user, problem=self, verdict_code=VerdictCode.AC).count() > 0 def num_submissions_by_user(self, user): """Number of user submissions to the problem""" return Submission.objects.filter(problem=self, user=user).count() def solved_n_position(self, position): """User (non-staff and active) who solved the problem in 'position' position""" active_students = get_user_model().objects.filter(is_staff=False, is_active=True) pks = Submission.objects.filter(problem=self, verdict_code=VerdictCode.AC, user__in=active_students) \ .order_by('user', 'pk').distinct('user').values_list('pk', flat=True) subs = Submission.objects.filter(pk__in=pks).order_by('pk')[position - 1:position] if len(subs) > 0 and subs[0] is not None: return subs[0].user return None def solved_first(self): """User (non-staff and active) who solved first""" return self.solved_n_position(1) def solved_second(self): """User (non-staff and active) who solved second""" return self.solved_n_position(2) def solved_third(self): """User (non-staff and active) who solved third""" return self.solved_n_position(3) def solved_position(self, user): """Position that user solved the problem (ignoring staff and inactive users). If not solved return None""" if self.solved_by_user(user): active_students = get_user_model().objects.filter(is_staff=False, is_active=True) iterator = 1 users_ac = (Submission.objects.filter( problem=self, user__in=active_students, verdict_code=VerdictCode.AC).order_by('pk', 'user').distinct( 'pk', 'user').values_list('user', flat=True)) for users in users_ac: if users == user.pk: return iterator iterator = iterator + 1 return None def insert_sql_list(self): """List containing all sql inserts""" return self.insert_sql.split(self.__INSERT_SEPARATION)
class PaymentMethod(models.Model): payment_processor = PaymentProcessorField( choices=PaymentProcessorManager.get_choices(), blank=False) customer = models.ForeignKey(Customer) added_at = models.DateTimeField(default=timezone.now) verified_at = models.DateTimeField(null=True, blank=True) data = JSONField(blank=True, null=True) objects = InheritanceManager() class States(object): Uninitialized = 'uninitialized' Unverified = 'unverified' Enabled = 'enabled' Disabled = 'disabled' Removed = 'removed' Choices = ((Uninitialized, 'Uninitialized'), (Unverified, 'Unverified'), (Enabled, 'Enabled'), (Disabled, 'Disabled'), (Removed, 'Removed')) @classmethod def as_list(cls): return [choice[0] for choice in cls.Choices] @classmethod def allowed_initial_states(cls): return [cls.Uninitialized, cls.Unverified, cls.Enabled] @classmethod def invalid_initial_states(cls): return list(set(cls.as_list()) - set(cls.allowed_initial_states())) state = FSMField(choices=States.Choices, default=States.Uninitialized) state_transitions = { 'initialize_unverified': { 'source': States.Uninitialized, 'target': States.Unverified }, 'initialize_enabled': { 'source': States.Uninitialized, 'target': States.Enabled }, 'verify': { 'source': States.Unverified, 'target': States.Enabled }, 'remove': { 'source': [States.Enabled, States.Disabled, States.Unverified], 'target': States.Removed }, 'disable': { 'source': States.Enabled, 'target': States.Disabled }, 'reenable': { 'source': States.Disabled, 'target': States.Enabled } } def __init__(self, *args, **kwargs): super(PaymentMethod, self).__init__(*args, **kwargs) if self.id: try: payment_method_class = self.payment_processor.payment_method_class if payment_method_class: self.__class__ = payment_method_class except AttributeError: pass @transition(field='state', **state_transitions['initialize_unverified']) def initialize_unverified(self, initial_data=None): pass @transition(field='state', **state_transitions['initialize_enabled']) def initialize_enabled(self, initial_data=None): pass @transition(field='state', **state_transitions['verify']) def verify(self): pass @transition(field='state', **state_transitions['remove']) def remove(self): """ Methods that implement this, need to remove the payment method from the real Payment Processor or raise TransitionNotAllowed """ pass @transition(field='state', **state_transitions['disable']) def disable(self): pass @transition(field='state', **state_transitions['reenable']) def reenable(self): pass def delete(self, using=None): if not self.state == self.States.Uninitialized: self.remove() super(PaymentMethod, self).delete(using=using) def pay_billing_document(self, document): if self.state == self.States.Enabled: self.payment_processor.pay_billing_document(document, self) else: raise PaymentMethodInvalid def __unicode__(self): return u'{} - {}'.format(self.customer, self.payment_processor)
class Resource(models.Model): '''Represents a resource, with importance to a given task or processtask. Attributes: :id (int): Private sequential identificator :start_date (datetime): Creation date :latest_update (datetime): Date of latest update :removed (boolean): Logical indicator of removal status of this resource ''' hash = models.CharField(max_length=50) ttype = models.CharField(max_length=100) create_date = models.DateTimeField(auto_now_add=True) latest_update = models.DateTimeField(auto_now=True) creator = models.ForeignKey(User) removed = models.BooleanField(default=False) # We need this to be able to properly guess the type objects = InheritanceManager() def clone(self): ''' Clones a resource, creating a copy and returning it ''' new_kwargs = dict([(fld.name, getattr(self, fld.name)) for fld in self._meta.fields if fld.name != self._meta.pk.name]) try: del new_kwargs['hash'] except: pass return self.__class__.objects.create(**new_kwargs) def remove(self): ''' Logically removes a Resource ''' self.removed = True self.save() def link(self): ''' Links a Resource to a Workflow. Until a Resource is linked he has no context. Unlinked resources will be periodically cleaned. ''' pass @staticmethod def all(subclasses=True, creator=None): ''' Returns all valid resource instances (excluding logically removed) ''' tmp = Resource.objects.filter(removed=False) if creator != None: tmp = tmp.filter(creator=creator) if subclasses: tmp = tmp.select_subclasses() return tmp def type(self): ''' Returns the specific resource type, polymorphically ''' return self._meta.app_label + '.' + self.__class__.__name__ def to_representation(self, instance): ''' Converts str to json for serialization. ''' serializer = self.get_serializer() return serializer.to_representation(instance) def to_internal_value(self, instance): ''' Converts json to str for model saving. ''' serializer = self.get_serializer() return serializer.to_internal_value(instance) @staticmethod def init_serializer(instance=None, data=None, many=False, partial=False): ''' Initializes a serializer able to process this specific type of Resource Each Resource child should specify this field. ''' from .api import ResourceSerializer return ResourceSerializer(instance=instance, data=data, many=many, partial=partial) def get_serializer(self): ''' Returns a serializer to process this kind of Resource ''' serializer_name = '__%s' % (self.type()) serializer = None if hasattr(Resource, serializer_name): serializer = getattr(Resource, serializer_name) else: serializer = self.init_serializer() setattr(Resource, serializer_name, serializer) return serializer class Meta: ordering = ['-id']
class Widget(models.Model): """ Defines a UI widget and the source datatables """ tables = models.ManyToManyField(Table) section = models.ForeignKey(Section) title = models.CharField(max_length=100) row = models.IntegerField() col = models.IntegerField() width = models.IntegerField(default=6) # setting height of 0 will let widget box auto-size to resulting data height = models.IntegerField(default=300) rows = models.IntegerField(default=-1) options = PickledObjectField() module = models.CharField(max_length=100) uiwidget = models.CharField(max_length=100) uioptions = PickledObjectField() # not globally unique, but should be sufficiently unique within a report slug = models.SlugField(max_length=100) # widget to be stacked below the previous widget on the same row stack_widget = models.BooleanField(default=False) objects = InheritanceManager() def __repr__(self): return '<Widget %s (%s)>' % (self.title, self.id) def __unicode__(self): return '<Widget %s (%s)>' % (self.title, self.id) def save(self, *args, **kwargs): self.slug = '%s-%d-%d' % (slugify(self.title), self.row, self.col) super(Widget, self).save(*args, **kwargs) @classmethod def create(cls, *args, **kwargs): options = kwargs.pop('options', None) table = kwargs.pop('table', None) w = Widget(*args, **kwargs) w.compute_row_col() if options: w.options = JsonDict(options) w.save() if table: w.tables.add(table) return w def get_definition(self, criteria): """Get dict of widget attributes for sending via JSON.""" report = self.section.report widget_def = { "widgettype": self.widgettype().split("."), "posturl": reverse('widget-job-list', args=(report.namespace, report.slug, self.slug)), "updateurl": reverse('widget-criteria', args=(report.namespace, report.slug, self.slug)), "options": self.uioptions, "widgetid": self.id, "widgetslug": self.slug, "row": self.row, "width": self.width, "height": self.height, "criteria": criteria, } return widget_def def widgettype(self): return '%s.%s' % (self.module.split('.')[-1], self.uiwidget) def table(self, i=0): return self.tables.all()[i] def compute_row_col(self): rowmax = self.section.report.widgets().aggregate(Max('row')) row = rowmax['row__max'] if row is None: row = 1 col = 1 elif self.stack_widget: # This widget needs to be stacked below the previous widget pre_w = self.section.report.widgets().order_by('-row', '-col')[0] if pre_w.width != self.width: raise ValueError("The stack widget with title '%s' should set " "with width %s." % (self.title, pre_w.width)) elif pre_w.title.lower() == self.title.lower(): raise ValueError("The stack widget title '%s' is the same as " "the previous widget, thus should be " "changed." % self.title) row = pre_w.row col = pre_w.col else: widthsum = (self.section.report.widgets().filter( row=row).aggregate(Sum('width'))) width = widthsum['width__sum'] if width + self.width > 12: row += 1 col = 1 else: col = width + 1 self.row = row self.col = col def collect_fields(self): # Gather up all fields fields = OrderedDict() # All fields attached to the section's report for f in self.section.report.fields.all().order_by('id'): fields[f.keyword] = f # All fields attached to the section for f in self.section.fields.all().order_by('id'): if f.keyword not in fields: fields[f.keyword] = f # All fields attached to any Widget's Tables for w in self.section.widget_set.all().order_by('id'): for t in w.tables.all(): for f in t.fields.all().order_by('id'): if f.keyword not in fields: fields[f.keyword] = f return fields
class TicketEvent(models.Model): objects = InheritanceManager() ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE) initiator = models.ForeignKey("User", on_delete=models.CASCADE, null=True) date_edited = models.DateTimeField(auto_now=True) date_created = models.DateTimeField(auto_now_add=True)
class Asset(TranslatedModel, LicensedModel, PublishedModel, TimestampedModel, AssetPermission): """A piece of content included in a story An asset could be an image, a block of text, an embedded resource represented by an HTML snippet or a media file. This is a base class that provides common metadata for the asset. However, it does not provide the fields that specify the content itself. Also, to reduce the number of database tables and queries this model class does not provide translated metadata fields. When creating an asset, one shouldn't instantiate this class, but instead use one of the model classes that inherits form Asset. """ asset_id = UUIDField(auto=True) type = models.CharField(max_length=10, choices=ASSET_TYPES) attribution = models.TextField(blank=True) source_url = models.URLField(blank=True) """The URL where an asset originated. It could be used to store the canonical URL for a resource that is not yet oEmbedable or the canonical URL of an article or tweet where text is quoted from. """ owner = models.ForeignKey(User, related_name="assets", blank=True, null=True) section_specific = models.BooleanField(default=False) datasets = models.ManyToManyField('DataSet', related_name='assets', blank=True) asset_created = models.DateTimeField(blank=True, null=True) """Date/time the non-digital version of an asset was created For example, the data a photo was taken """ translated_fields = ['title', 'caption'] translation_class = AssetTranslation # Use InheritanceManager from django-model-utils to make # fetching of subclassed objects easier objects = InheritanceManager() def __unicode__(self): subclass_obj = Asset.objects.get_subclass(pk=self.pk) return subclass_obj.__unicode__() @models.permalink def get_absolute_url(self): return ('asset_detail', [str(self.asset_id)]) def display_title(self): """ Wrapper to handle displaying some kind of title when the the title field is blank """ # For now just call the __unicode__() method return unicode(self) def render(self, format='html'): """Render a viewable representation of an asset Arguments: format -- the format to render the asset. defaults to 'html' which is presently the only available option. """ try: return getattr(self, "render_" + format).__call__() except AttributeError: return self.__unicode__() def render_thumbnail(self, width=None, height=None, format='html', **kwargs): """Render a thumbnail-sized viewable representation of an asset Arguments: height -- Height of the thumbnail in pixels width -- Width of the thumbnail in pixels format -- the format to render the asset. defaults to 'html' which is presently the only available option. """ return getattr(self, "render_thumbnail_" + format).__call__( width, height, **kwargs) def render_thumbnail_html(self, width=150, height=100, **kwargs): """ Render HTML for a thumbnail-sized viewable representation of an asset This just provides a dummy placeholder and should be implemented classes that inherit from Asset. Arguments: height -- Height of the thumbnail in pixels width -- Width of the thumbnail in pixels """ html_class = kwargs.get('html_class', "") return mark_safe("<div class='asset-thumbnail %s' " "style='height: %dpx; width: %dpx'>Asset Thumbnail</div>" % (html_class, height, width)) def get_thumbnail_url(self, width=150, height=100, **kwargs): """Return the URL of the Asset's thumbnail""" return None def dataset_html(self, label=_("Associated Datasets")): """Return an HTML list of associated datasets""" output = [] if self.datasets.count(): download_label = _("Download the data") output.append("<p class=\"datasets-label\">%s:</p>" % label) output.append("<ul class=\"datasets\">") for dataset in self.datasets.select_subclasses(): download_label = (_("Download the data") if dataset.links_to_file else _("View the data")) output.append("<li>%s <a href=\"%s\">%s</a></li>" % (dataset.title, dataset.download_url(), download_label)) output.append("</ul>") return mark_safe(u'\n'.join(output)) def full_caption_html(self, wrapper='figcaption'): """Return the caption and attribution text together""" output = "" if self.caption: output += "<div class='caption'>%s</div>" % (self.caption) if self.attribution: attribution = self.attribution if self.source_url: attribution = "<a href='%s'>%s</a>" % (self.source_url, attribution) output += "<div class='attribution'>%s</div>" % (attribution) dataset_html = self.dataset_html() if dataset_html: output += dataset_html if output: output = '<%s>%s</%s>' % (wrapper, output, wrapper) return output
class TrackerModel(models.Model): '''This is the base Model that tracker types subclass.''' max_connections = models.IntegerField( blank=True, default=8, help_text= "This is the maximum number of simultaneous connections that our bug importer will make to the tracker server." ) created_for_project = models.ForeignKey( 'search.Project', null=True, help_text= 'The project (if any) whose edit page caused the creation of this bug tracker model' ) # Every subclass provides the following attributes. We set # them to None, here in the superclass. # # Additionally, we spell _form, _urlmodel, and _urlform with # underscores, and we put strings inside. Then, we access those # via get_form(), get_urlmodel(), and get_urlform(). This may # look weird, but it saves us from an otherwise-painful circular # import situation. short_name = None namestr = None _form = None _urlmodel = None _urlform = None def importable_or_none(name): def actual_function(cls, name=name): value = getattr(cls, name) if value is None: return None module_name, member_name = value.rsplit('.', 1) module = importlib.import_module(module_name) return getattr(module, member_name) return actual_function get_form = classmethod(importable_or_none('_form')) get_urlmodel = classmethod(importable_or_none('_urlmodel')) get_urlform = classmethod(importable_or_none('_urlform')) # This optional attribute specifies a class name, which is intepreted as # part of the mysite.customs.core_bugimporters module. # # If the class is specified, we will use custom code from that class # when doing bug parsing. custom_parser = models.CharField( max_length=200, blank=True, default='', help_text= '(For use by OpenHatch admins) Choose a custom bug parser class for the tracker' ) objects = InheritanceManager() def as_dict(self): # First, add our data out_dict = django.forms.models.model_to_dict(self) # Then, indicate to downstream what kind of bug importer we are CLASS_NAME2SIMPLE_NAME = { mysite.customs.models.BugzillaTrackerModel: 'bugzilla', mysite.customs.models.GitHubTrackerModel: 'github', mysite.customs.models.GoogleTrackerModel: 'google', mysite.customs.models.JiraTrackerModel: 'jira', mysite.customs.models.RoundupTrackerModel: 'roundup', mysite.customs.models.TracTrackerModel: 'trac', mysite.customs.models.LaunchpadTrackerModel: 'launchpad', mysite.customs.models.TigrisTrackerModel: 'tigris', } out_dict['bugimporter'] = CLASS_NAME2SIMPLE_NAME[self.__class__] # Then, remove fields that we don't care about BLACKLISTED_FIELDS = set([ 'id', # This is not needed by the importer 'trackermodel_ptr', # This is not needed by the importer 'created_for_project', # Not needed by importer either 'old_trac', # This is useless 'max_connections', # This is useless ]) for field in BLACKLISTED_FIELDS: if field in out_dict: del out_dict[field] # Add a list of our queries. # This permits oh-bugimporters to go to the 'net and query the tracker # for new bugs that correspond to this bug tracker. query_urls = [] for querymodel in TrackerQueryModel.__subclasses__(): queries = querymodel.objects.filter(tracker=self) query_urls.extend([q.get_query_url() for q in queries]) out_dict['queries'] = query_urls # Add a list of bug URLs we're responsible for. # This permits oh-bugimporters to go to the 'net and refresh each bug. # It is essential because otherwise, when a bug falls out of a query # (if, for example, it becomes 'resolved' and the query only looks for # bugs that need fixing), we would not get up-to-date information about # the bug. out_dict['existing_bug_urls'] = list( mysite.search.models.Bug.all_bugs.filter( tracker_id=self.id).values_list('canonical_bug_link', flat=True)) # Some subclasses will add a get_older_bug_data value. This generic # method supports that by adding that key, and setting it to None. out_dict['get_older_bug_data'] = None return out_dict def get_edit_url(self): '''This method returns the URL you can use to access this tracker's edit link. It is part of this superclass so that derived classes can use the functionality without implementing it themselves. It relies on the subclasses having a short_name attribute.''' return reverse('mysite.customs.views.edit_tracker', kwargs={ 'tracker_id': self.id, 'tracker_type': self.short_name, 'tracker_name': self.tracker_name }) def get_base_url(self): # Implement this in a subclass raise NotImplementedError @staticmethod def get_by_name(tracker_model_name): '''This returns the right TrackerModel by looping across all the subclasses and returning the one that has the desired short_name.''' for candidate in TrackerModel.__subclasses__(): if candidate.short_name == tracker_model_name: return candidate raise ValueError("No TrackerModel known by name: %s" % (tracker_model_name, )) @classmethod def get_instance_by_name(cls, tracker_name): '''This returns the instance of a subclass of TrackerModel, if any, that has its tracker_name field set to the provided value. This is necessary because tracker_name is defined by each of the subclasses, rather than by this class in particular.''' query_parts = [] for subclass in cls.__subclasses__(): name = subclass.__name__.lower() query_as_dict = {name + '__tracker_name': tracker_name} query_parts.append(Q(**query_as_dict)) def _pipe_things(a, b): return a | b joined = reduce(_pipe_things, query_parts) return cls.objects.select_subclasses().get(joined) @classmethod def get_instance_by_id(cls, tracker_name): query_parts = [] for subclass in cls.__subclasses__(): name = subclass.__name__.lower() query_as_dict = {name + '__pk': tracker_name} query_parts.append(Q(**query_as_dict)) def _pipe_things(a, b): return a | b joined = reduce(_pipe_things, query_parts) return cls.objects.select_subclasses().get(joined)
class Comment(models.Model): """Comment in forum, articles, tutorial, chapter, etc.""" class Meta: verbose_name = 'Commentaire' verbose_name_plural = 'Commentaires' objects = InheritanceManager() author = models.ForeignKey(User, verbose_name='Auteur', related_name='comments', db_index=True) editor = models.ForeignKey(User, verbose_name='Editeur', related_name='comments-editor+', null=True, blank=True) ip_address = models.CharField('Adresse IP de l\'auteur ', max_length=39) position = models.IntegerField('Position', db_index=True) text = models.TextField('Texte') text_html = models.TextField('Texte en Html') like = models.IntegerField('Likes', default=0) dislike = models.IntegerField('Dislikes', default=0) pubdate = models.DateTimeField('Date de publication', auto_now_add=True, db_index=True) update = models.DateTimeField('Date d\'édition', null=True, blank=True) update_index_date = models.DateTimeField( 'Date de dernière modification pour la réindexation partielle', auto_now=True, db_index=True) is_visible = models.BooleanField('Est visible', default=True) text_hidden = models.CharField('Texte de masquage ', max_length=80, default='') hat = models.ForeignKey(Hat, verbose_name='Casquette', on_delete=models.SET_NULL, related_name='comments', blank=True, null=True) def update_content(self, text, on_error=None): _, old_metadata, _ = render_markdown(self.text) html, metadata, messages = render_markdown(text, on_error=on_error) self.text = text self.text_html = html self.save() all_the_pings = list( filter(lambda user_name: user_name != self.author.username, metadata.get('ping', []))) all_the_pings = list( set(all_the_pings) - set(old_metadata.get('ping', []))) max_ping_count = settings.ZDS_APP['comment']['max_pings'] first_pings = all_the_pings[:max_ping_count] for username in first_pings: pinged_user = User.objects.filter(username=username).first() if not pinged_user: continue signals.new_content.send(sender=self.__class__, instance=self, user=pinged_user) unpinged_usernames = set(old_metadata.get('ping', [])) - set(all_the_pings) unpinged_users = User.objects.filter( username__in=list(unpinged_usernames)) for unpinged_user in unpinged_users: signals.unsubscribe.send(self.author, instance=self, user=unpinged_user) def hide_comment_by_user(self, user, text_hidden): """Hide a comment and save it :param user: the user that hid the comment :param text_hidden: the hide reason :return: """ self.is_visible = False self.text_hidden = text_hidden self.editor = user self.save() def get_user_vote(self, user): """ Get a user vote (like, dislike or neutral) """ if user.is_authenticated(): try: user_vote = 'like' if CommentVote.objects.get( user=user, comment=self).positive else 'dislike' except CommentVote.DoesNotExist: user_vote = 'neutral' else: user_vote = 'neutral' return user_vote def set_user_vote(self, user, vote): """ Set a user vote (like, dislike or neutral) """ if vote == 'neutral': CommentVote.objects.filter(user=user, comment=self).delete() else: CommentVote.objects.update_or_create( user=user, comment=self, defaults={'positive': (vote == 'like')}) self.like = CommentVote.objects.filter(positive=True, comment=self).count() self.dislike = CommentVote.objects.filter(positive=False, comment=self).count() def get_votes(self, type=None): """ Get the non-anonymous votes """ if not hasattr(self, 'votes'): self.votes = CommentVote.objects.filter( comment=self, id__gt=settings.VOTES_ID_LIMIT).select_related('user').all() return self.votes def get_likers(self): """ Get the list of the users that liked this Comment """ return [vote.user for vote in self.get_votes() if vote.positive] def get_dislikers(self): """ Get the list of the users that disliked this Comment """ return [vote.user for vote in self.get_votes() if not vote.positive] def get_absolute_url(self): return Comment.objects.get_subclass(id=self.id).get_absolute_url() def __str__(self): return 'Comment by {}'.format(self.author.username)
class Status(OrderedCollectionPageMixin, BookWyrmModel): """any post, like a reply to a review, etc""" user = fields.ForeignKey("User", on_delete=models.PROTECT, activitypub_field="attributedTo") content = fields.HtmlField(blank=True, null=True) raw_content = models.TextField(blank=True, null=True) mention_users = fields.TagField("User", related_name="mention_user") mention_books = fields.TagField("Edition", related_name="mention_book") local = models.BooleanField(default=True) content_warning = fields.CharField(max_length=500, blank=True, null=True, activitypub_field="summary") privacy = fields.PrivacyField(max_length=255) sensitive = fields.BooleanField(default=False) # created date is different than publish date because of federated posts published_date = fields.DateTimeField(default=timezone.now, activitypub_field="published") edited_date = fields.DateTimeField(blank=True, null=True, activitypub_field="updated") deleted = models.BooleanField(default=False) deleted_date = models.DateTimeField(blank=True, null=True) favorites = models.ManyToManyField( "User", symmetrical=False, through="Favorite", through_fields=("status", "user"), related_name="user_favorites", ) reply_parent = fields.ForeignKey( "self", null=True, on_delete=models.PROTECT, activitypub_field="inReplyTo", ) thread_id = models.IntegerField(blank=True, null=True) objects = InheritanceManager() activity_serializer = activitypub.Note serialize_reverse_fields = [("attachments", "attachment", "id")] deserialize_reverse_fields = [("attachments", "attachment")] class Meta: """default sorting""" ordering = ("-published_date", ) def save(self, *args, **kwargs): """save and notify""" if self.reply_parent: self.thread_id = self.reply_parent.thread_id or self.reply_parent.id super().save(*args, **kwargs) if not self.reply_parent: self.thread_id = self.id super().save(broadcast=False, update_fields=["thread_id"]) def delete(self, *args, **kwargs): # pylint: disable=unused-argument """ "delete" a status""" if hasattr(self, "boosted_status"): # okay but if it's a boost really delete it super().delete(*args, **kwargs) return self.deleted = True # clear user content self.content = None if hasattr(self, "quotation"): self.quotation = None # pylint: disable=attribute-defined-outside-init self.deleted_date = timezone.now() self.save() @property def recipients(self): """tagged users who definitely need to get this status in broadcast""" mentions = [u for u in self.mention_users.all() if not u.local] if (hasattr(self, "reply_parent") and self.reply_parent and not self.reply_parent.user.local): mentions.append(self.reply_parent.user) return list(set(mentions)) @classmethod def ignore_activity(cls, activity): # pylint: disable=too-many-return-statements """keep notes if they are replies to existing statuses""" if activity.type == "Announce": try: boosted = activitypub.resolve_remote_id(activity.object, get_activity=True) except activitypub.ActivitySerializerError: # if we can't load the status, definitely ignore it return True # keep the boost if we would keep the status return cls.ignore_activity(boosted) # keep if it if it's a custom type if activity.type != "Note": return False # keep it if it's a reply to an existing status if cls.objects.filter(remote_id=activity.inReplyTo).exists(): return False # keep notes if they mention local users if activity.tag == MISSING or activity.tag is None: return True tags = [l["href"] for l in activity.tag if l["type"] == "Mention"] user_model = apps.get_model("bookwyrm.User", require_ready=True) for tag in tags: if user_model.objects.filter(remote_id=tag, local=True).exists(): # we found a mention of a known use boost return False return True @classmethod def replies(cls, status): """load all replies to a status. idk if there's a better way to write this so it's just a property""" return (cls.objects.filter( reply_parent=status).select_subclasses().order_by("published_date") ) @property def status_type(self): """expose the type of status for the ui using activity type""" return self.activity_serializer.__name__ @property def boostable(self): """you can't boost dms""" return self.privacy in ["unlisted", "public"] def to_replies(self, **kwargs): """helper function for loading AP serialized replies to a status""" return self.to_ordered_collection( self.replies(self), remote_id=f"{self.remote_id}/replies", collection_only=True, **kwargs, ).serialize() def to_activity_dataclass(self, pure=False): # pylint: disable=arguments-differ """return tombstone if the status is deleted""" if self.deleted: return activitypub.Tombstone( id=self.remote_id, url=self.remote_id, deleted=self.deleted_date.isoformat(), published=self.deleted_date.isoformat(), ) activity = ActivitypubMixin.to_activity_dataclass(self) activity.replies = self.to_replies() # "pure" serialization for non-bookwyrm instances if pure and hasattr(self, "pure_content"): activity.content = self.pure_content if hasattr(activity, "name"): activity.name = self.pure_name activity.type = self.pure_type book = getattr(self, "book", None) books = [book] if book else [] books += list(self.mention_books.all()) if len(books) == 1 and getattr(books[0], "preview_image", None): covers = [ activitypub.Document( url=fields.get_absolute_url(books[0].preview_image), name=books[0].alt_text, ) ] else: covers = [ activitypub.Document( url=fields.get_absolute_url(b.cover), name=b.alt_text, ) for b in books if b and b.cover ] activity.attachment = covers return activity def to_activity(self, pure=False): # pylint: disable=arguments-differ """json serialized activitypub class""" return self.to_activity_dataclass(pure=pure).serialize() def raise_not_editable(self, viewer): """certain types of status aren't editable""" # first, the standard raise super().raise_not_editable(viewer) if isinstance(self, (GeneratedNote, ReviewRating)): raise PermissionDenied() @classmethod def privacy_filter(cls, viewer, privacy_levels=None): queryset = super().privacy_filter(viewer, privacy_levels=privacy_levels) return queryset.filter(deleted=False) @classmethod def direct_filter(cls, queryset, viewer): """Overridden filter for "direct" privacy level""" return queryset.exclude(~Q(Q(user=viewer) | Q(mention_users=viewer)), privacy="direct") @classmethod def followers_filter(cls, queryset, viewer): """Override-able filter for "followers" privacy level""" return queryset.exclude( ~Q( # not yourself, a follower, or someone who is tagged Q(user__followers=viewer) | Q(user=viewer) | Q(mention_users=viewer)), privacy="followers", # and the status is followers only )
class ProposalBase(models.Model): objects = InheritanceManager() kind = models.ForeignKey(ProposalKind) title = models.CharField(_("Title"), max_length=100) description = models.TextField( _("Description"), max_length= 200, # @@@ need to enforce 400 in UI <= Can not change? I did it to 200. help_text=_("Please write up to 100 words. " "This is published pamphlet when it is accepted.")) abstract = models.TextField( _("Details Abstract"), help_text=_( "Please write detailed contents of your presentation to a maximum extent. " "This is published web site when it is accepted.")) additional_notes = models.TextField( _("Additional Notes"), blank=True, help_text=_("Anything else you'd like the program committee to know " "when making their selection: your past speaking " "experience, open source community experience, etc. " "This is not published. it is referenced by review.")) submitted = models.DateTimeField( default=datetime.datetime.now, editable=False, ) speaker = models.ForeignKey("speakers.Speaker", related_name="proposals") additional_speakers = models.ManyToManyField("speakers.Speaker", through="AdditionalSpeaker", blank=True) cancelled = models.BooleanField(default=False) tags = TaggableManager(blank=True) language = models.CharField( max_length=2, verbose_name=_("Language"), default=settings.LANGUAGES[0][0], choices=settings.LANGUAGES, ) def __unicode__(self): return self.title def can_edit(self): if hasattr(self, "presentation") and self.presentation: return False else: return True def get_tags_display(self): # No idea why django-taggit doesn't offer this itself return u", ".join(tag.name for tag in self.tags.all()) @property def section(self): return self.kind.section @property def speaker_email(self): return self.speaker.email @property def number(self): return str(self.pk).zfill(3) @property def status(self): try: return self.result.status except ObjectDoesNotExist: return 'undecided' def as_dict(self, details=False): """Return a dictionary representation of this proposal.""" # Put together the base dict. answer = { 'id': self.id, 'speakers': [i.as_dict for i in self.speakers()], 'status': self.status, 'title': self.title, } # Include details iff they're requested. if details: answer['details'] = { 'abstract': self.abstract, 'description': self.description, 'notes': self.additional_notes, } # If there is extra data that has been set, include it also. try: answer['extra'] = json.loads(self.data.data) except ObjectDoesNotExist: pass # Return the answer. return answer def speakers(self): yield self.speaker for speaker in self.additional_speakers.exclude( additionalspeaker__status=AdditionalSpeaker. SPEAKING_STATUS_DECLINED): yield speaker def notification_email_context(self): return { "title": self.title, "speaker": self.speaker.name, "speakers": ', '.join([x.name for x in self.speakers()]), "kind": self.kind.name, }
class InheritanceManagerTestChild1(InheritanceManagerTestParent): non_related_field_using_descriptor_2 = models.FileField(upload_to="test") normal_field_2 = models.TextField() objects = InheritanceManager()
class Project(models.Model): """ Base class for project types data_project stores a validated JSON representation of the project get_dataproject() method returns the python dict representation of the project """ owner = models.ForeignKey('authosm.OsmUser', db_column='owner') name = models.CharField(max_length=20, default='') created_date = models.DateTimeField(default=timezone.now) updated_date = models.DateTimeField(default=timezone.now, blank=True, null=True) info = models.TextField(default='No info') data_project = jsonfield.JSONField(default={}) """Stores a validated JSON representation of the project""" validated = models.BooleanField(default=False) #InheritanceManager objects = InheritanceManager() @classmethod def get_project_types(cls): global project_types return project_types @classmethod def add_project_type(cls, type, my_class): global project_types project_types[type] = my_class @classmethod def create_project(cls, name, user, validated, info, data_project): project = cls.objects.create(name=name, owner=user, validated=False, info=info, data_project=data_project) return project @classmethod def get_graph_model(cls, file_path): """Returns the model of the graph of the project type as a yaml object Returns an empty dict if there is no file with the model """ # file_path = GRAPH_MODEL_FULL_NAME graph_model = {} try: graph_model = Util.loadyamlfile(file_path) except Exception as e: log.exception(e) pass return graph_model def get_type(self): return "Base" #@classmethod def get_dataproject(self): """ Return the python dict representation of the project data """ #current_data = json.loads(self.data_project) current_data = Util.json_loads_byteified(self.data_project) return current_data #@classmethod def get_overview_data(self): result = { 'owner': self.owner, 'name': self.name, 'updated_date': self.updated_date, 'info': self.info, 'validated': self.validated } return result def set_data_project(self, new_data, validated): self.data_project = new_data self.set_validated(validated) self.update() def update(self): self.updated_date = timezone.now() self.save() def __str__(self): return self.name def edit_graph_positions(self, positions): # print positions try: current_data = json.loads(self.data_project) if 'positions' not in current_data: current_data['positions'] = {} if 'vertices' not in current_data['positions']: current_data['positions']['vertices'] = {} if 'vertices' in positions: current_data['positions']['vertices'].update( positions['vertices']) self.data_project = current_data self.update() result = True except Exception as e: log.debug(e) result = False return result def get_descriptors(self, type_descriptor): """Returns all descriptors of a given type""" try: current_data = json.loads(self.data_project) result = current_data[type_descriptor] except Exception as e: log.debug(e) result = {} return result def get_descriptor(self, descriptor_id, type_descriptor): """Returns a specific descriptor""" try: current_data = json.loads(self.data_project) result = current_data[type_descriptor][descriptor_id] print descriptor_id, type_descriptor, result except Exception as e: log.debug(e) result = {} return result def delete_descriptor(self, type_descriptor, descriptor_id): try: log.debug('delete descriptor' + descriptor_id + ' ' + type_descriptor) current_data = json.loads(self.data_project) del (current_data[type_descriptor][descriptor_id]) self.data_project = current_data self.update() result = True except Exception as e: log.debug(e) result = False return result def clone_descriptor(self, type_descriptor, descriptor_id, new_id): try: current_data = json.loads(self.data_project) descriptor = current_data[type_descriptor][descriptor_id] new_descriptor = self.get_clone_descriptor(descriptor, type_descriptor, new_id) current_data[type_descriptor][new_id] = new_descriptor self.data_project = current_data self.update() result = True except Exception as e: log.debug(e) result = False return result def edit_descriptor(self, type_descriptor, descriptor_id, new_data, data_type): try: ##FIXME questa parte va completamente rivista cosi' ha varie lacune #log.info('editing ',+ descriptor_id + ' ' + type_descriptor + ' ' + data_type) current_data = json.loads(self.data_project) new_descriptor = new_data if data_type == 'json': new_descriptor = json.loads(new_data) elif data_type == 'yaml': yaml_object = yaml.load(new_data) new_descriptor = json.loads(Util.yaml2json(yaml_object)) if type_descriptor != 'click' and type_descriptor != 'oshi' and type_descriptor != 'cran': reference_schema = self.get_json_schema_by_type( type_descriptor) Util.validate_json_schema(reference_schema, new_descriptor) current_data[type_descriptor][descriptor_id] = new_descriptor self.data_project = current_data self.update() result = True except Exception as e: log.debug(e) result = False return result def get_zip_archive(self): in_memory = StringIO() try: current_data = json.loads(self.data_project) zip = zipfile.ZipFile(in_memory, "w", zipfile.ZIP_DEFLATED) for desc_type in current_data: for current_desc in current_data[desc_type]: zip.writestr( current_desc + '.json', json.dumps(current_data[desc_type][current_desc])) zip.close() except Exception as e: log.debug(e) in_memory.flush() return in_memory def get_positions(self): """Returns the positions of nodes""" try: current_data = json.loads(self.data_project) positions = {} if 'positions' in current_data: positions = current_data['positions'] except Exception as e: log.debug(e) return positions def get_deployment_descriptor(self, **kwargs): """Returns the deployment descriptor""" raise NotImplementedError def get_node_overview(self, **kwargs): """Returns the node overview""" raise NotImplementedError def get_all_ns_descriptors(self, nsd_id): raise NotImplementedError def translate_push_ns_on_repository(self, translator, nsd_id, repository, **kwargs): raise NotImplementedError
class LineItem(models.Model): """An individual item that is either in a shopping cart or on an order. This model doesn't do anything by itself; you'll need to subclass it as described in the :doc:`Getting Started Guide <backend>`. """ cart = models.ForeignKey(Cart, related_name='items', blank=True, null=True, on_delete=models.PROTECT) order = models.ForeignKey(Order, related_name='items', blank=True, null=True, on_delete=models.CASCADE) total_when_charged = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True) objects = InheritanceManager() class Meta: # Because IDs auto increment, ordering by ID has the same effect as # ordering by date added, but we don't have to store the date ordering = ('id', ) @property def total(self): """The total cost for this line item. Returns the total actually charged to the customer if this item is attached to an :class:`~lorikeet.models.Order`, or calls :func:`~lorikeet.models.LineItem.get_total` otherwise. """ if self.order_id: return self.total_when_charged return self.get_total() def get_total(self): """Returns the total amount to charge on this LineItem. By default this raises ``NotImplemented``; subclasses of this class need to override this. If you want to know the total for this line item from your own code, use the :func:`~lorikeet.models.LineItem.total` property rather than calling this function. """ raise NotImplementedError( "Provide a get_total method in your LineItem " "subclass {}.".format(self.__class__.__name__)) def save(self, *args, **kwargs): if self.order is not None and not getattr(self, '_new_order'): raise ValueError("Cannot modify a cart item attached to an order.") return super().save(*args, **kwargs) def check_complete(self, for_checkout=False): """Checks that this line item is ready to be checked out. This method should raise :class:`~lorikeet.exceptions.IncompleteCartError` if the line item is not ready to be checked out (e.g. there is insufficient stock in inventory to fulfil this line item). By default it does nothing. :param for_checkout: Set to ``True`` when the cart is about to be checked out. See the documentation for :meth:`prepare_for_checkout` for more details. is going to be called within the current transaction, so you should use things like `select_for_update <https://docs.djangoproject.com/en/1.10/ref/models/querysets/#select-for-update>`_. :type for_checkout: bool """ def prepare_for_checkout(self): """Prepare this line item for checkout.
class ProposalBase(models.Model): objects = InheritanceManager() kind = models.ForeignKey(ProposalKind) title = models.CharField(max_length=100) description = models.TextField( _("Brief Description"), max_length=400, # @@@ need to enforce 400 in UI help_text=_( "If your proposal is accepted this will be made public and printed in the " "program. Should be one paragraph, maximum 400 characters.")) abstract = models.TextField( _("Detailed Abstract"), help_text= _("Detailed outline. Will be made public if your proposal is accepted." )) additional_notes = models.TextField( blank=True, help_text=_( "Anything else you'd like the program committee to know when making their " "selection: your past experience, etc. This is not made public.")) submitted = models.DateTimeField( default=now, editable=False, ) speaker = models.ForeignKey(Speaker, related_name="proposals") additional_speakers = models.ManyToManyField(Speaker, through="AdditionalSpeaker", blank=True) cancelled = models.BooleanField(default=False) def can_edit(self): return True @property def section(self): return self.kind.section @property def speaker_email(self): return self.speaker.email @property def number(self): return str(self.pk).zfill(3) def speakers(self): yield self.speaker speakers = self.additional_speakers.exclude( additionalspeaker__status=AdditionalSpeaker. SPEAKING_STATUS_DECLINED) for speaker in speakers: yield speaker def notification_email_context(self): return { "title": self.title, "speaker": self.speaker.name, "kind": self.kind.name, }
class Adjustment(models.Model): """An adjustment to the total on a cart. Subclass this model only for adjustments that users can add to their carts (e.g. discount codes). This model doesn't do anything by itself; you'll need to subclass it. """ cart = models.ForeignKey(Cart, related_name='adjustments', blank=True, null=True, on_delete=models.CASCADE) order = models.ForeignKey(Order, related_name='adjustments', blank=True, null=True, on_delete=models.CASCADE) total_when_charged = models.DecimalField(max_digits=7, decimal_places=2, blank=True, null=True) objects = InheritanceManager() @property def total(self): """The total cost for this line item. Returns the total actually charged to the customer if this adjustment is attached to an :class:`~lorikeet.models.Order`, or calls :func:`~lorikeet.models.Adjustment.get_total` otherwise. """ if self.order_id: return self.total_when_charged return self.get_total() def get_total(self, subtotal=None): """Returns the total adjustment to make to the cart. By default this raises :class:`NotImplementedError`; subclasses will need to override this. If you want to know the total for this adjustment from your own code, use the :func:`~lorikeet.models.Adjustment.total` property rather than calling this function. :param subtotal: The subtotal of all line items, which is passed in as a convenience. :type subtotal: decimal.Decimal :return: The amount to add to the cart. This value can be (and in most cases, will be) negative, in order to represent a discount. :rtype: decimal.Decimal """ raise NotImplementedError("Provide a get_total method in your " "Adjustment subclass {}.".format( self.__class__.__name__)) def check_complete(self, for_checkout=False): """Checks that this adjustment is ready to be checked out. This method should raise :class:`~lorikeet.exceptions.IncompleteCartError` if the line item is not ready to be checked out (e.g. there is insufficient stock in inventory to fulfil this line item). By default it does nothing. :param for_checkout: Set to ``True`` when the cart is about to be checked out. See the documentation for :meth:`prepare_for_checkout` for more details. is going to be called within the current transaction, so you should use things like `select_for_update <https://docs.djangoproject.com/en/1.10/ref/models/querysets/#select-for-update>`_. :type for_checkout: bool """ def prepare_for_checkout(self): """Prepare this adjustment for checkout.
class Question(models.Model): """ Base class for all question types. Shared properties placed here. """ quiz = models.ManyToManyField(Quiz, verbose_name=_("Quiz"), blank=True) category = models.ForeignKey(Category, verbose_name=_("Category"), blank=True, null=True, on_delete=models.CASCADE) figure = models.ImageField(upload_to='uploads/%Y/%m/%d', blank=True, null=True, verbose_name=_("Figure")) content = models.CharField(max_length=1000, blank=False, help_text=_("Enter the question text that " "you want displayed"), verbose_name=_('Question')) explanation = models.TextField(max_length=2000, blank=True, help_text=_("Explanation to be shown " "after the question has " "been answered."), verbose_name=_('Explanation')) answer_order = models.CharField( max_length=30, null=True, blank=True, choices=ANSWER_ORDER_OPTIONS, help_text="The order in which multichoice \ answer options are displayed \ to the user", verbose_name="Answer Order") content = models.CharField(max_length=1000, blank=False, help_text="Enter the answer text that \ you want displayed", verbose_name="Content") correct = models.BooleanField(blank=False, default=False, help_text="Is this a correct answer?", verbose_name="Correct") #answers = models.BooleanField(max_length=40, null=False, blank = False, help_text="Answer Choices", verbose_name="Choise") def check_if_correct(self, guess): answer = Answer.objects.get(id=guess) if answer.correct is True: return True else: return False def order_answers(self, queryset): if self.answer_order == 'content': return queryset.order_by('content') # if self.answer_order == 'random': # return queryset.order_by('Random') if self.answer_order == 'none': return queryset.order_by('None') def get_answers(self): return self.order_answers(Answer.objects.filter(question=self)) def get_answers_list(self): return [(answer.id, answer.content) for answer in self.order_answers(Answer.objects.filter(question=self))] def answer_choice_to_string(self, guess): return Answer.objects.get(id=guess).content class Meta: verbose_name = "Multiple Choice Question" verbose_name_plural = "Multiple Choice Questions" objects = InheritanceManager()
class TethysJob(models.Model): """ Base class for all job types. This is intended to be an abstract class that is not directly instantiated. """ class Meta: verbose_name = 'Job' objects = InheritanceManager() STATUSES = ( ('PEN', 'Pending'), ('SUB', 'Submitted'), ('RUN', 'Running'), ('COM', 'Complete'), ('ERR', 'Error'), ('ABT', 'Aborted'), ('VAR', 'Various'), ('VCP', 'Various-Complete'), ('RES', 'Results-Ready'), ) STATUS_DICT = {k: v for v, k in STATUSES} VALID_STATUSES = [v for v, _ in STATUSES] name = models.CharField(max_length=1024) description = models.CharField(max_length=2048, blank=True, default='') user = models.ForeignKey(User, on_delete=models.CASCADE) label = models.CharField(max_length=1024) creation_time = models.DateTimeField(auto_now_add=True) execute_time = models.DateTimeField(blank=True, null=True) start_time = models.DateTimeField(blank=True, null=True) completion_time = models.DateTimeField(blank=True, null=True) workspace = models.CharField(max_length=1024, default='') extended_properties = JSONField(default=dict, null=True, blank=True) _process_results_function = models.CharField(max_length=1024, blank=True, null=True) _status = models.CharField(max_length=3, choices=STATUSES, default=STATUSES[0][0]) @property def type(self): """ Returns the name of Tethys Job type. """ return self.__class__.__name__ @property def update_status_interval(self): if not hasattr(self, '_update_status_interval'): self._update_status_interval = datetime.timedelta(seconds=10) return self._update_status_interval @property def last_status_update(self): if not getattr(self, '_last_status_update', None): self._last_status_update = self.execute_time or timezone.now() - self.update_status_interval return self._last_status_update @property def status(self): self.update_status() field = self._meta.get_field('_status') status = self._get_FIELD_display(field) return status @status.setter def status(self, value): self.update_status(status=value) @property def run_time(self): start_time = self.start_time or self.execute_time if start_time: end_time = self.completion_time or datetime.datetime.now(start_time.tzinfo) run_time = end_time - start_time else: return '' return run_time def execute(self, *args, **kwargs): """ executes the job """ self._execute(*args, **kwargs) self.execute_time = timezone.now() self._status = 'SUB' self.save() def update_status(self, status=None, *args, **kwargs): """ Update status of job. """ old_status = self._status # Set status from status given if status: if status not in self.VALID_STATUSES: log.error('Invalid status given: {}'.format(status)) return self._status = status self.save() # Update status if status not given and still pending/running elif old_status in ['PEN', 'SUB', 'RUN', 'VAR'] and self.is_time_to_update(): self._update_status(*args, **kwargs) self._last_status_update = timezone.now() # Post-process status after update if old status was pending/running if old_status in ['PEN', 'SUB', 'RUN', 'VAR']: if self._status == 'RUN' and (old_status == 'PEN' or old_status == 'SUB'): self.start_time = timezone.now() if self._status in ["COM", "VCP", "RES"]: self.process_results() elif self._status == 'ERR' or self._status == 'ABT': self.completion_time = timezone.now() self.save() def is_time_to_update(self): """ Check if it is time to update again. Returns: bool: True if update_status_interval or longer has elapsed since our last update, else False. """ time_since_last_update = timezone.now() - self.last_status_update is_time_to_update = time_since_last_update > self.update_status_interval return is_time_to_update @property def process_results_function(self): """ Returns: A function handle or None if function cannot be resolved. """ if self._process_results_function: function_extractor = TethysFunctionExtractor(self._process_results_function, None) if function_extractor.valid: return function_extractor.function @process_results_function.setter def process_results_function(self, function): if isinstance(function, str): self._process_results_function = function return module_path = inspect.getmodule(function).__name__.split('.') module_path.append(function.__name__) self._process_results_function = '.'.join(module_path) def process_results(self, *args, **kwargs): """ Process the results. """ log.debug('Started processing results for job: {}'.format(self)) self._process_results(*args, **kwargs) self.completion_time = timezone.now() self._status = 'COM' self.save() log.debug('Finished processing results for job: {}'.format(self)) @abstractmethod def _execute(self, *args, **kwargs): pass @abstractmethod def _update_status(self, *args, **kwargs): pass @abstractmethod def _process_results(self, *args, **kwargs): pass @abstractmethod def stop(self): """ Stops job from executing """ raise NotImplementedError() @abstractmethod def pause(self): """ Pauses job during execution """ raise NotImplementedError() @abstractmethod def resume(self): """ Resumes a job that has been paused """ raise NotImplementedError()
class Unit(models.Model): sim = models.CharField(max_length=64, null=True, blank=True) objects = InheritanceManager()
class PaymentMethod(models.Model): class PaymentProcessors: @classmethod def as_choices(cls): for name in settings.PAYMENT_PROCESSORS.keys(): yield (name, name) @classmethod def as_list(cls): return [name for name in settings.PAYMENT_PROCESSORS.keys()] payment_processor = models.CharField( choices=PaymentProcessors.as_choices(), blank=False, null=False, max_length=256) customer = models.ForeignKey(Customer) added_at = models.DateTimeField(default=timezone.now) data = JSONField(blank=True, null=True, default={}) verified = models.BooleanField(default=False) canceled = models.BooleanField(default=False) valid_until = models.DateTimeField(null=True, blank=True) display_info = models.CharField(max_length=256, null=True, blank=True) objects = InheritanceManager() class Meta: ordering = ['-id'] @property def final_fields(self): return ['payment_processor', 'customer', 'added_at'] @property def irreversible_fields(self): return ['verified', 'canceled'] def __init__(self, *args, **kwargs): super(PaymentMethod, self).__init__(*args, **kwargs) if self.id: try: payment_method_class = self.get_payment_processor( ).payment_method_class if payment_method_class: self.__class__ = payment_method_class except AttributeError: pass @property def transactions(self): return self.transaction_set.all() def get_payment_processor(self): return payment_processors.get_instance(self.payment_processor) def delete(self, using=None): if not self.state == self.States.Uninitialized: self.remove() super(PaymentMethod, self).delete(using=using) def encrypt_data(self, data): key = settings.PAYMENT_METHOD_SECRET return Fernet(key).encrypt(bytes(data)) def decrypt_data(self, crypted_data): key = settings.PAYMENT_METHOD_SECRET try: return str(Fernet(key).decrypt(bytes(crypted_data))) except InvalidToken: return None def cancel(self): if self.canceled: raise ValidationError( "You can't cancel a canceled payment method.") cancelable_states = [ Transaction.States.Initial, Transaction.States.Pending ] transactions = self.transactions.filter(state__in=cancelable_states) errors = [] for transaction in transactions: if transaction.state == Transaction.States.Initial: try: transaction.cancel() except TransitionNotAllowed: errors.append("Transaction {} couldn't be canceled".format( transaction.uuid)) if transaction.state == Transaction.States.Pending: payment_processor = self.get_payment_processor() if (hasattr(payment_processor, 'void_transaction') and not payment_processor.void_transaction(transaction)): errors.append("Transaction {} couldn't be voided".format( transaction.uuid)) transaction.save() if errors: return errors self.canceled = True self.save() return None def clean_with_previous_instance(self, previous_instance): if not previous_instance: return for field in self.final_fields: old_value = getattr(previous_instance, field, None) current_value = getattr(self, field, None) if old_value != current_value: raise ValidationError("Field '%s' may not be changed." % field) for field in self.irreversible_fields: old_value = getattr(previous_instance, field, None) current_value = getattr(self, field, None) if old_value and old_value != current_value: raise ValidationError( "Field '%s' may not be changed anymore." % field) def full_clean(self, *args, **kwargs): previous_instance = kwargs.pop('previous_instance', None) super(PaymentMethod, self).full_clean(*args, **kwargs) self.clean_with_previous_instance(previous_instance) # this assumes that nobody calls clean and then modifies this object # without calling clean again setattr(self, '.cleaned', True) @property def allowed_currencies(self): return self.get_payment_processor().allowed_currencies @property def public_data(self): return {} def __unicode__(self): return u'{} - {}'.format(self.customer, self.get_payment_processor_display())
class Character(Model): objects = InheritanceManager() character = OneToOneField(ObjectDB, on_delete=CASCADE, primary_key=True) strength = IntegerField(default=1) dexterity = IntegerField(default=1) stamina = IntegerField(default=1) intelligence = IntegerField(default=1) wits = IntegerField(default=1) resolve = IntegerField(default=1) presence = IntegerField(default=1) manipulation = IntegerField(default=1) composure = IntegerField(default=1) academics = IntegerField(default=0) computer = IntegerField(default=0) crafts = IntegerField(default=0) investigation = IntegerField(default=0) medicine = IntegerField(default=0) occult = IntegerField(default=0) politics = IntegerField(default=0) science = IntegerField(default=0) athletics = IntegerField(default=0) brawl = IntegerField(default=0) drive = IntegerField(default=0) firearms = IntegerField(default=0) larceny = IntegerField(default=0) stealth = IntegerField(default=0) survival = IntegerField(default=0) weaponry = IntegerField(default=0) animal_ken = IntegerField(default=0) empathy = IntegerField(default=0) expression = IntegerField(default=0) intimidation = IntegerField(default=0) persuasion = IntegerField(default=0) socialize = IntegerField(default=0) streetwise = IntegerField(default=0) subterfuge = IntegerField(default=0) size = IntegerField(default=5) temporary_willpower = IntegerField(default=0) permanent_willpower = IntegerField(default=0) @property def maximum_willpower(self): return self.get('wits') + self.get('resolve') - self.permanent_willpower @property def current_willpower(self): return self.maximum_willpower - self.temporary_willpower bashing = IntegerField(default=0) lethal = IntegerField(default=0) aggravated = IntegerField(default=0) @property def maximum_health(self): return self.get('size') + self.get('stamina') + self.get_bonuses('health') @property def undamaged(self): return max(self.maximum_health - self.bashing - self.lethal - self.aggravated, 0) @property def speed(self): return 5 + self.get('strength') + self.get('dexterity') @property def initiative(self): return self.get('dexterity') + self.get('composure') @property def defense(self): return min(self.get('dexterity'), self.get('wits')) + self.get('athletics') def get(self, stat): current = getattr(self, stat) if isinstance(current, int): return current + self.get_bonuses(stat) else: return current def get_bonuses(self, stat): return 0 def display_stat(self, name, width=20): display_name = name.replace('_', ' ') display_name = display_name.title() display_name = display_name.ljust(width - 2) value = self.get(name) return f'{display_name} {value}' @property def display_attributes(self): return f""" {self.display_stat('intelligence')} {self.display_stat('strength')} {self.display_stat('presence')} {self.display_stat('wits')} {self.display_stat('dexterity')} {self.display_stat('manipulation')} {self.display_stat('resolve')} {self.display_stat('stamina')} {self.display_stat('composure')} """.strip() @property def display_skills(self): return f""" {self.display_stat('academics')} {self.display_stat('athletics')} {self.display_stat('animal_ken')} {self.display_stat('computer')} {self.display_stat('brawl')} {self.display_stat('empathy')} {self.display_stat('crafts')} {self.display_stat('drive')} {self.display_stat('expression')} {self.display_stat('investigation')} {self.display_stat('firearms')} {self.display_stat('intimidation')} {self.display_stat('medicine')} {self.display_stat('larceny')} {self.display_stat('persuasion')} {self.display_stat('occult')} {self.display_stat('stealth')} {self.display_stat('socialize')} {self.display_stat('politics')} {self.display_stat('survival')} {self.display_stat('streetwise')} {self.display_stat('science')} {self.display_stat('weaponry')} {self.display_stat('subterfuge')} """.strip() @property def mental_attributes(self): return sum([ self.intelligence, self.wits, self.resolve ]) @property def physical_attributes(self): return sum([ self.strength, self.dexterity, self.stamina ]) @property def social_attributes(self): return sum([ self.presence, self.manipulation, self.composure ]) @property def mental_skills(self): return sum([ self.academics, self.computer, self.crafts, self.investigation, self.medicine, self.occult, self.politics, self.science ]) @property def physical_skills(self): return sum([ self.athletics, self.brawl, self.drive, self.firearms, self.larceny, self.stealth, self.survival, self.weaponry ]) @property def social_skills(self): return sum([ self.animal_ken, self.empathy, self.expression, self.intimidation, self.persuasion, self.socialize, self.streetwise, self.subterfuge ])
class Record(models.Model): visitor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) visited_at = models.DateTimeField(auto_now_add=True) objects = InheritanceManager()
class OrderItem(models.Model): """ This is the basic interface for order items. Order items are line items that fill up the shopping carts and orders. Each implementation of OrderItem should provide its own purchased_callback as a method. """ objects = InheritanceManager() order = models.ForeignKey(Order, db_index=True) # this is denormalized, but convenient for SQL queries for reports, etc. user should always be = order.user user = models.ForeignKey(User, db_index=True) # this is denormalized, but convenient for SQL queries for reports, etc. status should always be = order.status status = models.CharField(max_length=32, default='cart', choices=ORDER_STATUSES) qty = models.IntegerField(default=1) unit_cost = models.DecimalField(default=0.0, decimal_places=2, max_digits=30) line_desc = models.CharField(default="Misc. Item", max_length=1024) currency = models.CharField(default="usd", max_length=8) # lower case ISO currency codes fulfilled_time = models.DateTimeField(null=True) @property def line_cost(self): """ Return the total cost of this OrderItem """ return self.qty * self.unit_cost @classmethod def add_to_order(cls, order, *args, **kwargs): """ A suggested convenience function for subclasses. NOTE: This does not add anything to the cart. That is left up to the subclasses to implement for themselves """ # this is a validation step to verify that the currency of the item we # are adding is the same as the currency of the order we are adding it # to currency = kwargs.get('currency', 'usd') if order.currency != currency and order.orderitem_set.exists(): raise InvalidCartItem( _("Trying to add a different currency into the cart")) @transaction.commit_on_success def purchase_item(self): """ This is basically a wrapper around purchased_callback that handles modifying the OrderItem itself """ self.purchased_callback() self.status = 'purchased' self.fulfilled_time = datetime.now(pytz.utc) self.save() def purchased_callback(self): """ This is called on each inventory item in the shopping cart when the purchase goes through. """ raise NotImplementedError @property def single_item_receipt_template(self): """ The template that should be used when there's only one item in the order """ return 'shoppingcart/receipt.html' @property def single_item_receipt_context(self): """ Extra variables needed to render the template specified in `single_item_receipt_template` """ return {} @property def additional_instruction_text(self): """ Individual instructions for this order item. Currently, only used for e-mails. """ return ''
class Widget(models.Model): """ Defines a UI widget and the source datatables """ tables = models.ManyToManyField(Table) section = models.ForeignKey(Section) title = models.CharField(max_length=100) row = models.IntegerField() col = models.IntegerField() width = models.IntegerField(default=1) height = models.IntegerField(default=300) rows = models.IntegerField(default=-1) options = PickledObjectField() module = models.CharField(max_length=100) uiwidget = models.CharField(max_length=100) uioptions = PickledObjectField() # not globally unique, but should be sufficiently unique within a report slug = models.SlugField(max_length=100) objects = InheritanceManager() def __repr__(self): return '<Widget %s (%s)>' % (self.title, self.id) def __unicode__(self): return '<Widget %s (%s)>' % (self.title, self.id) def save(self, *args, **kwargs): self.slug = '%s-%d-%d' % (slugify(self.title), self.row, self.col) super(Widget, self).save(*args, **kwargs) def get_definition(self, criteria): """Get dict of widget attributes for sending via JSON.""" report = self.section.report widget_def = { "widgettype": self.widgettype().split("."), "posturl": reverse('widget-job-list', args=(report.namespace, report.slug, self.slug)), "updateurl": reverse('widget-criteria', args=(report.namespace, report.slug, self.slug)), "options": self.uioptions, "widgetid": self.id, "widgetslug": self.slug, "row": self.row, "width": self.width, "height": self.height, "criteria": criteria, } return widget_def def widgettype(self): return '%s.%s' % (self.module.split('.')[-1], self.uiwidget) def table(self, i=0): return self.tables.all()[i] def compute_row_col(self): rowmax = self.section.report.widgets().aggregate(Max('row')) row = rowmax['row__max'] if row is None: row = 1 col = 1 else: widthsum = (self.section.report.widgets().filter( row=row).aggregate(Sum('width'))) width = widthsum['width__sum'] if width + self.width > 12: row += 1 col = 1 else: col = width + 1 self.row = row self.col = col def collect_fields(self): # Gather up all fields fields = SortedDict() # All fields attached to the section's report for f in self.section.report.fields.all().order_by('id'): fields[f.keyword] = f # All fields attached to the section for f in self.section.fields.all().order_by('id'): if f.keyword not in fields: fields[f.keyword] = f # All fields attached to any Widget's Tables for w in self.section.widget_set.all().order_by('id'): for t in w.tables.all(): for f in t.fields.all().order_by('id'): if f.keyword not in fields: fields[f.keyword] = f return fields
class BasicArticle(models.Model): allowed = models.BooleanField(default=True) author = models.ForeignKey(User, on_delete=models.PROTECT, related_name='articles', null=True, blank=True) title = models.CharField(max_length=256, default='') text = models.TextField() pubdate = models.DateTimeField('date published', default=datetime.now) pluses = models.ManyToManyField(User, blank=True, related_name='plused_articles') minuses = models.ManyToManyField(User, blank=True, related_name='minused_articles') views = models.ManyToManyField(User, blank=True, related_name='viewed_articles') allow_comments = models.BooleanField(default=True, null=True) comments = models.OneToOneField(CommentsList, on_delete=models.CASCADE, null=True, blank=True, related_name="article") files = models.OneToOneField(ArticleFilesList, on_delete=models.CASCADE, null=True, blank=True, related_name="article") rating = models.IntegerField(default=0) objects = InheritanceManager() def class_name(self): return self.__class__.__name__ def has_rights(self, user): return False def can_react(self, user): if self.__class__.__name__ != 'CommunityArticle': return user.is_authenticated and self.can_see_article(user) and self.author != user return user.is_authenticated and self.can_see_article(user) def update_rating(self): article = self.get_child() article.rating = article.pluses.count() - article.minuses.count() article.save() def increase_rating(self): article = self.get_child() self.rating += 1 if article.__class__.__name__ == 'PersonalArticle': article.author.profile.rating += 1 article.author.profile.save() elif article.__class__.__name__ == 'PersonalInCommunityArticle': article.author.profile.rating += 1 article.group.rating += 1 article.author.profile.save() article.group.save() elif article.__class__.__name__ == 'CommunityArticle': article.group.rating += 1 article.group.save() self.save() def decrease_rating(self): article = self.get_child() self.rating -= 1 if article.__class__.__name__ == 'PersonalArticle': article.author.profile.rating -= 1 article.author.profile.save() elif article.__class__.__name__ == 'PersonalInCommunityArticle': article.author.profile.rating -= 1 article.group.rating -= 1 article.author.profile.save() article.group.save() elif article.__class__.__name__ == 'CommunityArticle': article.group.rating -= 1 article.group.save() self.save() def can_see_article(self, user=None): return False def can_edit_article(self, user): if self.__class__.__name__ != 'PersonalArticle': return self.group.has_power(user) or ( not self.allowed and self.__class__.__name__ == 'PersonalInCommunityArticle' and self.author == user) return user == self.author def can_comment_article(self, user): return self.allow_comments and self.can_see_article(user) def get_child(self): return BasicArticle.objects.get_subclass(id=self.id) def get_child_type(self): return BasicArticle.objects.get_subclass(id=self.id).class_name() def plus(self, user): if user not in self.pluses.all(): self.pluses.add(user) self.increase_rating() if user in self.minuses.all(): self.minuses.remove(user) self.increase_rating() self.save() def minus(self, user): if user in self.pluses.all(): self.pluses.remove(user) self.decrease_rating() if user not in self.minuses.all(): self.minuses.add(user) self.decrease_rating() self.save() def remove_plus(self, user): if user in self.pluses.all(): self.pluses.remove(user) self.decrease_rating() self.save() def remove_minus(self, user): if user in self.minuses.all(): self.minuses.remove(user) self.increase_rating() self.save() def get_rating(self): return self.pluses.count() - self.minuses.count() def __str__(self): if self.title: return self.title return self.text
class ProductVariant(models.Model, Item): sku = models.CharField(pgettext_lazy('Variant field', 'SKU'), max_length=32, unique=True) name = models.CharField(pgettext_lazy('Variant field', 'variant name'), max_length=100, blank=True) price_override = PriceField(pgettext_lazy('Variant field', 'price override'), currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2, blank=True, null=True) weight_override = WeightField(pgettext_lazy('Variant field', 'weight override'), unit=settings.DEFAULT_WEIGHT, max_digits=6, decimal_places=2, blank=True, null=True) product = models.ForeignKey(Product, related_name='variants') attributes = JSONField(pgettext_lazy('Variant field', 'attributes'), default={}) images = models.ManyToManyField('ProductImage', through='VariantImage') objects = InheritanceManager() class Meta: app_label = 'product' def __str__(self): return self.name or self.sku def get_weight(self): return self.weight_override or self.product.weight def check_quantity(self, quantity): available_quantity = self.get_stock_quantity() if quantity > available_quantity: raise InsufficientStock(self) def get_stock_quantity(self): if not len(self.stock.all()): return 0 return max([stock.quantity_available for stock in self.stock.all()]) def get_price_per_item(self, discounts=None, **kwargs): price = self.price_override or self.product.price if discounts: discounts = list(get_variant_discounts(self, discounts, **kwargs)) if discounts: price = min(price | discount for discount in discounts) return price def get_absolute_url(self): slug = self.product.get_slug() product_id = self.product.id return reverse('product:details', kwargs={ 'slug': slug, 'product_id': product_id }) def as_data(self): return { 'product_name': str(self), 'product_id': self.product.pk, 'variant_id': self.pk, 'unit_price': str(self.get_price_per_item().gross) } def is_shipping_required(self): return True def is_in_stock(self): return any( [stock.quantity_available > 0 for stock in self.stock.all()]) def get_attribute(self, pk): return self.attributes.get(str(pk)) def display_variant(self, attributes=None): if attributes is None: attributes = self.product.attributes.all() values = get_attributes_display_map(self, attributes).values() if values: return ', '.join([smart_text(value) for value in values]) else: return smart_text(self) def display_product(self, attributes=None): return '%s (%s)' % (smart_text( self.product), self.display_variant(attributes=attributes)) def select_stockrecord(self, quantity=1): # By default selects stock with lowest cost price stock = filter(lambda stock: stock.quantity_available >= quantity, self.stock.all()) stock = sorted(stock, key=lambda stock: stock.cost_price, reverse=True) if stock: return stock[0] def get_cost_price(self): stock = self.select_stockrecord() if stock: return stock.cost_price