class NotCoveredFields(models.Model): json = JSONField()
class CallbackEvent(models.Model): """Model used to log all callbacks.""" # ref to the webhook that picked up the event webhook = models.ForeignKey(Webhook) # events are read-only so just a timestamp required timestamp = models.DateTimeField() # the Trello event type - moveCard, commentCard, etc. event_type = models.CharField(max_length=50) # the complete request payload, as JSON event_payload = JSONField() def __unicode__(self): if self.id: return ("CallbackEvent %i: '%s' raised by webhook %s." % (self.id, self.event_type, self.webhook.id)) else: return ("CallbackEvent: '%s' raised by webhook %s." % (self.event_type, self.webhook.id)) def __str__(self): return unicode(self).encode('utf-8') def __repr__(self): return (u"<CallbackEvent id=%s, webhook=%s, event_type='%s'>" % (self.id, self.webhook_id, self.event_type)) def save(self, *args, **kwargs): """Update timestamp""" self.timestamp = timezone.now() super(CallbackEvent, self).save(*args, **kwargs) return self @property def action_data(self): """Returns the 'data' node from the payload.""" return self.event_payload.get('action', {}).get('data') @property def member(self): """Returns 'memberCreator' JSON extracted from event_payload.""" return self.event_payload.get('action', {}).get('memberCreator') @property def board(self): """Returns 'board' JSON extracted from event_payload.""" return self.action_data.get('board') if self.action_data else None @property def list(self): """Returns 'list' JSON extracted from event_payload.""" return self.action_data.get('list') if self.action_data else None @property def card(self): """Returns 'card' JSON extracted from event_payload.""" return self.action_data.get('card') if self.action_data else None @property def member_name(self): """Return member name if it exists (used in admin).""" return self.member.get('fullName') if self.member else None @property def board_name(self): """Return board name if it exists (used in admin).""" return self.board.get('name') if self.board else None @property def list_name(self): """Return list name if it exists (used in admin).""" return self.list.get('name') if self.list else None @property def card_name(self): """Return card name if it exists (used in admin).""" return self.card.get('name') if self.card else None @property def template(self): """Return full path to render template, based on event_type.""" return 'trello_webhooks/%s.html' % self.event_type def render(self): """Render the event using an HTML template. The path to the template comes from the `template` property, which is derived from the event_type. If the template does not exist (typically this would be because we capture a new event type that we haven't previously encountered), a warning is logged, and None is returned. (We return None instead of an empty string to make it clear that something has gone wrong - an empty string _could_ be a realistic output, if someone has overridden a template and spelled the context vars incorrectly.) The event_payload is passed in to the template as the context. Default templates exist for most event types, but you are encouraged to override these templates in your own project (see the template property for the full path to the template that is loaded). """ try: return render_to_string(self.template, self.event_payload) except TemplateDoesNotExist: logger.warning(u"Missing or misconfigured template: '%s'", self.template) return None
class Entity(DirtyFieldsMixin, models.Model): resource = models.ForeignKey(Resource) string = models.TextField() string_plural = models.TextField(blank=True) key = models.TextField(blank=True) # Needed for webL10n comment = models.TextField(blank=True) order = models.PositiveIntegerField(default=0) source = JSONField(blank=True, default=list) # List of paths to source code files obsolete = models.BooleanField(default=False) changed_locales = models.ManyToManyField( Locale, through='ChangedEntityLocale', help_text='List of locales in which translations for this entity have ' 'changed since the last sync.') @property def marked(self): return utils.mark_placeables(self.string) @property def marked_plural(self): return utils.mark_placeables(self.string_plural) def __unicode__(self): return self.string def has_changed(self, locale): """ Check if translations in the given locale have changed since the last sync. """ return locale in self.changed_locales.all() def mark_changed(self, locale): """ Mark the given locale as having changed translations since the last sync. """ ChangedEntityLocale.objects.get_or_create(entity=self, locale=locale) def add_translation(self, string, locale, user): """ Add a new translation for this entity. If a matching translation already exists, mark it as unfuzzy, and if the given user is a translator, approve it. """ try: translation = self.translation_set.get(locale=locale, string=string) except Translation.DoesNotExist: translation = Translation(entity=self, locale=locale, user=user, string=string, fuzzy=True) if translation.pk: translation.fuzzy = False if user.has_perm('base.can_localize') and not translation.approved: translation.approved = True translation.approved_user = user translation.approved_date = timezone.now() translation.save() def get_translation(self, plural_form=None): """Get fetched translation of a given entity.""" translations = self.fetched_translations if plural_form is not None: translations = [ t for t in translations if t.plural_form == plural_form ] if translations: translation = sorted(translations, key=lambda k: (not k.approved, k.date))[0] return { 'fuzzy': translation.fuzzy, 'string': translation.string, 'approved': translation.approved, 'pk': translation.pk } else: return { 'fuzzy': False, 'string': None, 'approved': False, 'pk': None } @classmethod def for_project_locale(self, project, locale, paths=None): """Get project entities with locale translations.""" entities = self.objects.filter(resource__project=project, resource__stats__locale=locale, obsolete=False) if paths: try: subpage = Subpage.objects.get(project=project, name__in=paths) paths = subpage.resources.values_list("path") except Subpage.DoesNotExist: pass entities = entities.filter(resource__path__in=paths) or entities entities = entities.prefetch_related( 'resource', Prefetch('translation_set', queryset=Translation.objects.filter(locale=locale), to_attr='fetched_translations')) entities_array = [] for entity in entities: translation_array = [] if entity.string_plural == "": translation_array.append(entity.get_translation()) else: for plural_form in range(0, locale.nplurals or 1): translation_array.append( entity.get_translation(plural_form)) entities_array.append({ 'pk': entity.pk, 'original': entity.string, 'marked': entity.marked, 'original_plural': entity.string_plural, 'marked_plural': entity.marked_plural, 'key': entity.key, 'path': entity.resource.path, 'format': entity.resource.format, 'comment': entity.comment, 'order': entity.order, 'source': entity.source, 'obsolete': entity.obsolete, 'translation': translation_array, }) return sorted(entities_array, key=lambda k: k['order'])
class DiscussionsConfiguration(TimeStampedModel): """ Associates a learning context with discussion provider and configuration """ context_key = LearningContextKeyField( primary_key=True, db_index=True, unique=True, max_length=255, # Translators: A key specifying a course, library, program, # website, or some other collection of content where learning # happens. verbose_name=_("Learning Context Key"), ) enabled = models.BooleanField( default=True, help_text= _("If disabled, the discussions in the associated learning context/course will be disabled." )) lti_configuration = models.ForeignKey( LtiConfiguration, on_delete=models.SET_NULL, blank=True, null=True, help_text=_("The LTI configuration data for this context/provider."), ) plugin_configuration = JSONField( blank=True, default={}, help_text=_( "The plugin configuration data for this context/provider."), ) provider_type = models.CharField( blank=False, max_length=100, verbose_name=_("Discussion provider"), help_text=_("The discussion tool/provider's id"), ) history = HistoricalRecords() def clean(self): """ Validate the model Currently, this only support courses, this can be extended whenever discussions are available in other contexts """ if not CourseOverview.course_exists(self.context_key): raise ValidationError( 'Context Key should be an existing learning context.') def __str__(self): return "DiscussionsConfiguration(context_key='{context_key}', provider='{provider}', enabled={enabled})".format( context_key=self.context_key, provider=self.provider_type, enabled=self.enabled, ) def supports(self, feature: str) -> bool: """ Check if the provider supports some feature """ features = AVAILABLE_PROVIDER_MAP.get( self.provider_type)['features'] or [] has_support = bool(feature in features) return has_support @classmethod def is_enabled(cls, context_key: CourseKey) -> bool: """ Check if there is an active configuration for a given course key Default to False, if no configuration exists """ configuration = cls.get(context_key) return configuration.enabled # pylint: disable=undefined-variable @classmethod def get(cls, context_key: CourseKey) -> cls: """ Lookup a model by context_key """ try: configuration = cls.objects.get(context_key=context_key) except cls.DoesNotExist: configuration = cls( context_key=context_key, enabled=False, provider_type=DEFAULT_PROVIDER_TYPE, ) return configuration # pylint: enable=undefined-variable @property def available_providers(self) -> list[str]: return ProviderFilter.current( course_key=self.context_key).available_providers @classmethod def get_available_providers(cls, context_key: CourseKey) -> list[str]: return ProviderFilter.current( course_key=context_key).available_providers
class Project(models.Model): MANUAL = "NA" DAILY = "D" WEEKLY = "W" FORTNIGHT = "F" MONTHLY = "M" SCHEDULES = [ (MANUAL, "Manual"), (DAILY, "Daily"), (WEEKLY, "Weekly"), (FORTNIGHT, "Fortnightly"), (MONTHLY, "Monthyl"), ] name = models.CharField(max_length=255) type = models.ForeignKey(ProjectType, verbose_name='Type of Project', null=True, blank=True) sector = models.ForeignKey(Sector, verbose_name='Sector', null=True, blank=True, related_name='project_sector') sub_sector = models.ForeignKey(Sector, verbose_name='Sub-Sector', null=True, blank=True, related_name='project_sub_sector') phone = models.CharField(max_length=255, blank=True, null=True) fax = models.CharField(max_length=255, blank=True, null=True) email = models.EmailField(blank=True, null=True) address = models.TextField(blank=True, null=True) website = models.URLField(blank=True, null=True) donor = models.CharField(max_length=256, blank=True, null=True) public_desc = models.TextField("Public Description", blank=True, null=True) additional_desc = models.TextField("Additional Description", blank=True, null=True) organization = models.ForeignKey(Organization, related_name='projects') logo = models.ImageField(upload_to="logo", default="logo/default_project_image.jpg") is_active = models.BooleanField(default=True) location = PointField(geography=True, srid=4326, blank=True, null=True) date_created = models.DateTimeField(auto_now_add=True, blank=True) cluster_sites = models.BooleanField(default=False) site_meta_attributes = JSONField(default=list) site_basic_info = JSONField(default={}) site_featured_images = JSONField(default=list) gsuit_meta = JSONField(default={}) gsuit_sync = models.CharField(choices=SCHEDULES, default=MANUAL, max_length=2) gsuit_sync_date = models.DateField(blank=True, null=True) gsuit_sync_end_of_month = models.BooleanField(default=False) # gsuit_meta sample = {'site_progress':{'link':'', 'last_updated':''}} logs = GenericRelation('eventlog.FieldSightLog') all_objects = ProjectAllManager() objects = ProjectManager() geo_layers = models.ManyToManyField('geo.GeoLayer', blank=True) class Meta: ordering = [ '-is_active', 'name', ] @property def latitude(self): if self.location: return self.location.y @property def longitude(self): if self.location: return self.location.x def getname(self): return self.name def __unicode__(self): return u'{}'.format(self.name) @property def get_staffs(self): staffs = self.project_roles.filter( group__name__in=["Reviewer", "Project Manager"]) return staffs @property def get_staffs_both_role(self): managers_id = self.project_roles.filter( group__name="Project Manager").values_list('user__id', flat=True) reviewers_id = self.project_roles.filter( group__name="Reviewer").values_list('user__id', flat=True) both = list(set(managers_id).intersection(reviewers_id)) return both def get_organization_name(self): return self.organization.name def get_project_type(self): return self.type.name @property def status(self): if self.project_instances.filter(form_status=1).count(): return 1 elif self.project_instances.filter(form_status=2).count(): return 2 elif self.project_instances.filter(form_status=0).count(): return 0 elif self.project_instances.filter(form_status=3).count(): return 3 return 4 def get_project_submission(self): instances = self.project_instances.all().order_by('-date') outstanding, flagged, approved, rejected = [], [], [], [] for submission in instances: if submission.form_status == 0: outstanding.append(submission) elif submission.form_status == 1: rejected.append(submission) elif submission.form_status == 2: flagged.append(submission) elif submission.form_status == 3: approved.append(submission) return outstanding, flagged, approved, rejected def get_submissions_count(self): qs = self.project_instances.aggregate( outstanding=Count( Case( When(form_status=0, project=self, then=1), output_field=IntegerField(), )), flagged=Count( Case( When(form_status=2, project=self, then=1), output_field=IntegerField(), )), approved=Count( Case( When(form_status=3, project=self, then=1), output_field=IntegerField(), )), rejected=Count( Case( When(form_status=1, project=self, then=1), output_field=IntegerField(), )), ) return qs.get('outstanding', 0), qs.get('flagged', 0), qs.get('approved', 0), qs.get('rejected', 0) def get_absolute_url(self): return "/fieldsight/application/#/project-dashboard/{}".format(self.pk)
class Shop(ChangeProtected, TranslatableShuupModel): protected_fields = ["currency", "prices_include_tax"] change_protect_message = _( "The following fields cannot be changed since there are existing orders for this shop" ) created_on = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('created on')) modified_on = models.DateTimeField(auto_now=True, editable=False, db_index=True, verbose_name=_('modified on')) identifier = InternalIdentifierField(unique=True, max_length=128) domain = models.CharField( max_length=128, blank=True, null=True, unique=True, verbose_name=_("domain"), help_text= _("Your shop domain name. Use this field to configure the URL that is used to visit your site. " "Note: this requires additional configuration through your internet domain registrar." )) status = EnumIntegerField( ShopStatus, default=ShopStatus.DISABLED, verbose_name=_("status"), help_text=_( "Your shop status. Disable your shop if it is no longer in use.")) owner = models.ForeignKey("Contact", blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_("contact")) options = JSONField(blank=True, null=True, verbose_name=_("options")) currency = CurrencyField( default=_get_default_currency, verbose_name=_("currency"), help_text= _("The primary shop currency. This is the currency used when selling your products." )) prices_include_tax = models.BooleanField( default=True, verbose_name=_("prices include tax"), help_text= _("This option defines whether product prices entered in admin include taxes. " "Note this behavior can be overridden with contact group pricing.")) logo = FilerImageField(verbose_name=_("logo"), blank=True, null=True, on_delete=models.SET_NULL, help_text=_("Shop logo. Will be shown at theme."), related_name="shop_logos") favicon = FilerImageField( verbose_name=_("favicon"), blank=True, null=True, on_delete=models.SET_NULL, help_text=_( "Shop favicon. Will be shown next to the address on browser."), related_name="shop_favicons") maintenance_mode = models.BooleanField( verbose_name=_("maintenance mode"), default=False, help_text= _("Check this if you would like to make your shop temporarily unavailable while you do some shop maintenance." )) contact_address = models.ForeignKey("MutableAddress", verbose_name=_("contact address"), blank=True, null=True, on_delete=models.SET_NULL) staff_members = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name="+", verbose_name=_('staff members')) labels = models.ManyToManyField("Label", blank=True, related_name="shops", verbose_name=_("labels")) translations = TranslatedFields( name=models.CharField( max_length=64, verbose_name=_("name"), help_text=_( "The shop name. This name is displayed throughout admin.")), public_name=models.CharField( max_length=64, verbose_name=_("public name"), help_text= _("The public shop name. This name is displayed in the store front and in any customer email correspondence." )), description=models.TextField( blank=True, verbose_name=_('description'), help_text=_( "To make your shop stand out, give it an awesome description. " "This is what will help your shoppers learn about your shop. " "It will also help shoppers find your store from the web.")), short_description=models.CharField( max_length=150, blank=True, verbose_name=_('short description'), help_text= _("Enter a short description for your shop. " "The short description will be used to get the attention of your " "customer with a small but precise description of your shop.")), maintenance_message=models.CharField( max_length=300, blank=True, verbose_name=_("maintenance message"), help_text= _("The message to display to customers while your shop is in maintenance mode." ))) objects = ShopManager() class Meta: verbose_name = _('shop') verbose_name_plural = _('shops') def __str__(self): return self.safe_translation_getter("name", default="Shop %d" % self.pk) def create_price(self, value): """ Create a price with given value and settings of this shop. Takes the ``prices_include_tax`` and ``currency`` settings of this Shop into account. :type value: decimal.Decimal|int|str :rtype: shuup.core.pricing.Price """ if self.prices_include_tax: return TaxfulPrice(value, self.currency) else: return TaxlessPrice(value, self.currency) def _are_changes_protected(self): return Order.objects.filter(shop=self).exists()
class JSONFieldWithDefaultTestModel(models.Model): json = JSONField(default={"sukasuka": "YAAAAAZ"}) class Meta: app_label = 'jsonfield'
class EmailKind(models.Model): """ This model represents a kind of email. Created and configured manually through the admin panel. """ MIN_NAME_LENGTH = 6 name = models.CharField(max_length=255, verbose_name='email kind name') language = models.CharField(max_length=2, choices=settings.LANGUAGES, default=settings.DEFAULT_LANGUAGE_CODE, verbose_name='language') description = models.CharField(max_length=300, verbose_name='description', default='') template = models.TextField(verbose_name='template') plain_template = models.TextField(verbose_name='plain text template') default_context = JSONField( load_kwargs={'object_pairs_hook': collections.OrderedDict}, blank=True, verbose_name='default template context', default={} ) default_sender = models.CharField( max_length=255, blank=True, verbose_name='default from (can be named format)' ) default_recipients = models.TextField( blank=True, verbose_name='default to (comma separated, can be named format)' ) default_subject = models.TextField(blank=True, verbose_name='default subject') default_reply_to = models.TextField( blank=True, verbose_name='default reply to (comma separated, can be named format)' ) active = models.BooleanField(default=True, verbose_name='Is active') fragments = models.ManyToManyField( 'EmailKindFragment', related_name='kinds', blank=True ) model_name = 'EmailKind' class Meta: unique_together = ('name', 'language') ordering = ['name', 'language'] def __str__(self): return '{}/{}'.format(self.name, self.language) def iter_all_images(self): for img in self.images.all(): yield img for fragment in self.fragments.all(): for img in fragment.images.all(): yield img def generate_entry(self, params): """ Creates and persists an EmailEntry given the current EmailKind. Checks that both the EmailKind and the params for EmailEntry are consistent. @type params **dict @param params Dictionary containing data to build the EmailEntry from the current EmailKind. Example: { 'sender': '*****@*****.**', 'recipients': ['*****@*****.**', '*****@*****.**', ...], 'reply_to': ['*****@*****.**', '*****@*****.**', ...], 'customer_id': '3838383', 'subject': 'This is the email subject', 'context': {'first_name': 'Troll', ...}, 'send_at': 1434029573, # Unix timestamp, UTC 'check_url': 'http://myservice.qdqmedia.com/canisend/4983/323' 'attachs': [{ 'filename': 'invoice.pdf', 'content_type': 'application/pdf', 'content': 'raw content of the file' }, ...] } """ try: self.assert_well_formed() self.assert_sane_params(params) except EmailAssertionError as e: logger.exception("The EmailKind: {ek} is not well formed or was badly called with: {pa}"\ .format(ek=self, pa=params)) raise e context = params.get('context', None) or self.default_context sender = params.get('sender', None) or self.default_sender recipients = ','.join(params.get('recipients', [])) or \ self.default_recipients subject = params.get('subject', None) or self.default_subject reply_to = ','.join(params.get('reply_to', [])) or \ self.default_reply_to with transaction.atomic(): entry = EmailEntry.objects.create( kind=self, customer_id=params.get('customer_id', ''), context=context, sender=sender, recipients=recipients, subject=subject, reply_to=reply_to, send_at=params.get('send_at', None), check_url=params.get('check_url', ''), backend=params.get('backend', ''), metadata=params.get('meta_fields', {}), ) attachs = params.get('attachs', []) for attach in attachs: uploaded_file = SimpleUploadedFile( attach['filename'], give_me_bytes(base64.b64decode(attach['content'])), attach['content_type'] ) Attachment.objects.create(entry=entry, attached_file=uploaded_file, content_type=attach['content_type'], name=attach['filename']) return entry def assert_well_formed(self): cassert(len(self.name) >= self.MIN_NAME_LENGTH, 'kind: {}. too short name'.format(self)) # TODO: Do something here when language policy is clear. # assert self.language in allowed_language_codes(), \ # 'kind: {}. language not recognized'.format(self) cassert(len(self.template) > 0, 'kind: {}. empty template'.format(self)) cassert(len(self.plain_template) > 0, 'kind: {}. empty plain template'.format(self)) def assert_sane_params(self, params): cassert(self.default_sender or ('sender' in params and params['sender']), 'kind: {}. sender never specified'.format(self)) cassert(self.default_recipients or \ ('recipients' in params and params['recipients'] and \ any(r for r in params['recipients'])), 'kind: {}. recipients never specified'.format(self)) cassert(self.default_subject or \ ('subject' in params and params['subject']), 'kind: {}. subject never specified'.format(self)) if 'backend' in params: flattened_backends = sum(settings.CUSTOM_EMAIL_BACKENDS, ()) cassert( params['backend'] in flattened_backends, 'kind: {}. backend {} not known'.format(self, params['backend']) )
class Cart(models.Model): """A shopping cart.""" status = models.CharField( max_length=32, choices=CartStatus.CHOICES, default=CartStatus.OPEN) created = models.DateTimeField(auto_now_add=True) last_status_change = models.DateTimeField(auto_now_add=True) user = models.ForeignKey( settings.AUTH_USER_MODEL, blank=True, null=True, related_name='carts', on_delete=models.CASCADE) email = models.EmailField(blank=True, null=True) token = models.UUIDField(primary_key=True, default=uuid4, editable=False) voucher = models.ForeignKey( 'discount.Voucher', null=True, related_name='+', on_delete=models.SET_NULL) checkout_data = JSONField(null=True, editable=False) total = PriceField( currency=settings.DEFAULT_CURRENCY, max_digits=12, decimal_places=2, default=0) quantity = models.PositiveIntegerField(default=0) objects = CartQueryset.as_manager() class Meta: ordering = ('-last_status_change',) def __init__(self, *args, **kwargs): self.discounts = kwargs.pop('discounts', None) super().__init__(*args, **kwargs) def update_quantity(self): """Recalculate cart quantity based on lines.""" total_lines = self.count()['total_quantity'] if not total_lines: total_lines = 0 self.quantity = total_lines self.save(update_fields=['quantity']) def change_status(self, status): """Change cart status.""" # FIXME: investigate replacing with django-fsm transitions if status not in dict(CartStatus.CHOICES): raise ValueError('Not expected status') if status != self.status: self.status = status self.last_status_change = now() self.save() def change_user(self, user): """Assign cart to a user. If the user already has an open cart assigned, cancel it. """ open_cart = find_open_cart_for_user(user) if open_cart is not None: open_cart.change_status(status=CartStatus.CANCELED) self.user = user self.save(update_fields=['user']) def is_shipping_required(self): """Return `True` if any of the lines requires shipping.""" return any(line.is_shipping_required() for line in self.lines.all()) def __repr__(self): return 'Cart(quantity=%s)' % (self.quantity,) def __len__(self): return self.lines.count() # pylint: disable=R0201 def get_subtotal(self, item, **kwargs): """Return the cost of a cart line.""" return item.get_total(**kwargs) def get_total(self, **kwargs): """Return the total cost of the cart prior to shipping.""" subtotals = [ self.get_subtotal(item, **kwargs) for item in self.lines.all()] if not subtotals: raise AttributeError('Calling get_total() on an empty item set') zero = Price(0, currency=settings.DEFAULT_CURRENCY) return sum(subtotals, zero) def count(self): """Return the total quantity in cart.""" lines = self.lines.all() return lines.aggregate(total_quantity=models.Sum('quantity')) def clear(self): """Remove the cart.""" self.delete() def create_line(self, variant, quantity, data): """Create a cart line for given variant, quantity and optional data. The `data` parameter may be used to differentiate between items with different customization options. """ return self.lines.create( variant=variant, quantity=quantity, data=data or {}) def get_line(self, variant, data=None): """Return a line matching the given variant and data if any.""" all_lines = self.lines.all() if data is None: data = {} line = [line for line in all_lines if line.variant_id == variant.id and line.data == data] if line: return line[0] def add(self, variant, quantity=1, data=None, replace=False, check_quantity=True): """Add a product vartiant to cart. The `data` parameter may be used to differentiate between items with different customization options. If `replace` is truthy then any previous quantity is discarded instead of added to. """ cart_line, dummy_created = self.lines.get_or_create( variant=variant, defaults={'quantity': 0, 'data': data or {}}) if replace: new_quantity = quantity else: new_quantity = cart_line.quantity + quantity if new_quantity < 0: raise ValueError('%r is not a valid quantity (results in %r)' % ( quantity, new_quantity)) if check_quantity: variant.check_quantity(new_quantity) cart_line.quantity = new_quantity if not cart_line.quantity: cart_line.delete() else: cart_line.save(update_fields=['quantity']) self.update_quantity() def partition(self): """Split the card into a list of groups for shipping.""" grouper = ( lambda p: 'physical' if p.is_shipping_required() else 'digital') return partition(self.lines.all(), grouper, ProductGroup)
class Build(models.Model): """Build data.""" project = models.ForeignKey( Project, verbose_name=_('Project'), related_name='builds', ) version = models.ForeignKey( Version, verbose_name=_('Version'), null=True, related_name='builds', ) type = models.CharField( _('Type'), max_length=55, choices=BUILD_TYPES, default='html', ) state = models.CharField( _('State'), max_length=55, choices=BUILD_STATE, default='finished', ) date = models.DateTimeField(_('Date'), auto_now_add=True) success = models.BooleanField(_('Success'), default=True) setup = models.TextField(_('Setup'), null=True, blank=True) setup_error = models.TextField(_('Setup error'), null=True, blank=True) output = models.TextField(_('Output'), default='', blank=True) error = models.TextField(_('Error'), default='', blank=True) exit_code = models.IntegerField(_('Exit code'), null=True, blank=True) commit = models.CharField( _('Commit'), max_length=255, null=True, blank=True, ) _config = JSONField(_('Configuration used in the build'), default=dict) length = models.IntegerField(_('Build Length'), null=True, blank=True) builder = models.CharField( _('Builder'), max_length=255, null=True, blank=True, ) cold_storage = models.NullBooleanField( _('Cold Storage'), help_text='Build steps stored outside the database.', ) # Manager objects = BuildQuerySet.as_manager() CONFIG_KEY = '__config' class Meta: ordering = ['-date'] get_latest_by = 'date' index_together = [['version', 'state', 'type']] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._config_changed = False @property def previous(self): """ Returns the previous build to the current one. Matching the project and version. """ date = self.date or timezone.now() if self.project is not None and self.version is not None: return (Build.objects.filter( project=self.project, version=self.version, date__lt=date, ).order_by('-date').first()) return None @property def config(self): """ Get the config used for this build. Since we are saving the config into the JSON field only when it differs from the previous one, this helper returns the correct JSON used in this Build object (it could be stored in this object or one of the previous ones). """ if self.CONFIG_KEY in self._config: return Build.objects.get(pk=self._config[self.CONFIG_KEY])._config return self._config @config.setter def config(self, value): """ Set `_config` to value. `_config` should never be set directly from outside the class. """ self._config = value self._config_changed = True def save(self, *args, **kwargs): # noqa """ Save object. To save space on the db we only save the config if it's different from the previous one. If the config is the same, we save the pk of the object that has the **real** config under the `CONFIG_KEY` key. """ if self.pk is None or self._config_changed: previous = self.previous # yapf: disable if ( previous is not None and self._config and self._config == previous.config ): # yapf: enable previous_pk = previous._config.get(self.CONFIG_KEY, previous.pk) self._config = {self.CONFIG_KEY: previous_pk} super().save(*args, **kwargs) self._config_changed = False def __str__(self): return ugettext( 'Build {project} for {usernames} ({pk})'.format( project=self.project, usernames=' '.join( self.project.users.all().values_list('username', flat=True), ), pk=self.pk, ), ) def get_absolute_url(self): return reverse('builds_detail', args=[self.project.slug, self.pk]) @property def finished(self): """Return if build has a finished state.""" return self.state == BUILD_STATE_FINISHED @property def is_stale(self): """Return if build state is triggered & date more than 5m ago.""" mins_ago = timezone.now() - datetime.timedelta(minutes=5) return self.state == BUILD_STATE_TRIGGERED and self.date < mins_ago
class ProjectDebugFile(Model): __core__ = False file = FlexibleForeignKey('sentry.File') object_name = models.TextField() cpu_name = models.CharField(max_length=40) project = FlexibleForeignKey('sentry.Project', null=True) debug_id = models.CharField(max_length=64, db_column='uuid') data = JSONField(null=True) objects = ProjectDebugFileManager() class Meta: index_together = (('project', 'debug_id'), ) db_table = 'sentry_projectdsymfile' app_label = 'sentry' __repr__ = sane_repr('object_name', 'cpu_name', 'debug_id') @property def dif_type(self): ct = self.file.headers.get('Content-Type', 'unknown').lower() return KNOWN_DIF_TYPES.get(ct, 'unknown') @property def file_extension(self): if self.dif_type == 'breakpad': return '.sym' if self.dif_type == 'macho': return '.dSYM' if self.dif_type == 'proguard': return '.txt' if self.dif_type == 'elf': return '.debug' return '' @property def supports_caches(self): return ProjectSymCacheFile.computes_from(self) \ or ProjectCfiCacheFile.computes_from(self) @property def features(self): return frozenset((self.data or {}).get('features', [])) def delete(self, *args, **kwargs): dif_id = self.id with mysql_disabled_integrity(db=ProjectDebugFile.objects.db): with transaction.atomic(): # First, delete the debug file entity. This ensures no other # worker can attach caches to it. Integrity checks are deferred # within this transaction, so existing caches stay intact. super(ProjectDebugFile, self).delete(*args, **kwargs) # Explicitly select referencing caches and delete them. Using # the backref does not work, since `dif.id` is None after the # delete. symcaches = ProjectSymCacheFile.objects \ .filter(debug_file_id=dif_id) \ .select_related('cache_file') for symcache in symcaches: symcache.delete() cficaches = ProjectCfiCacheFile.objects \ .filter(debug_file_id=dif_id) \ .select_related('cache_file') for cficache in cficaches: cficache.delete() self.file.delete()
class Config(UuidAuditedModel): """ Set of configuration values applied as environment variables during runtime execution of the Application. """ owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) app = models.ForeignKey('App', on_delete=models.CASCADE) values = JSONField(default={}, blank=True) memory = JSONField(default={}, blank=True) cpu = JSONField(default={}, blank=True) tags = JSONField(default={}, blank=True) registry = JSONField(default={}, blank=True) healthcheck = JSONField(default={}, blank=True) class Meta: get_latest_by = 'created' ordering = ['-created'] unique_together = (('app', 'uuid'),) def __str__(self): return "{}-{}".format(self.app.id, str(self.uuid)[:7]) def _migrate_legacy_healthcheck(self): """ Get all healthchecks options together for use in scheduler """ # return if no legacy healthcheck is found if 'HEALTHCHECK_URL' not in self.values.keys(): return path = self.values.get('HEALTHCHECK_URL', '/') timeout = int(self.values.get('HEALTHCHECK_TIMEOUT', 50)) delay = int(self.values.get('HEALTHCHECK_INITIAL_DELAY', 50)) period_seconds = int(self.values.get('HEALTHCHECK_PERIOD_SECONDS', 10)) success_threshold = int(self.values.get('HEALTHCHECK_SUCCESS_THRESHOLD', 1)) failure_threshold = int(self.values.get('HEALTHCHECK_FAILURE_THRESHOLD', 3)) self.healthcheck['livenessProbe'] = { 'initialDelaySeconds': delay, 'timeoutSeconds': timeout, 'periodSeconds': period_seconds, 'successThreshold': success_threshold, 'failureThreshold': failure_threshold, 'httpGet': { 'path': path, } } def set_registry(self): # lower case all registry options for consistency self.registry = {key.lower(): value for key, value in self.registry.copy().items()} # PORT must be set if private registry is being used if self.registry and self.values.get('PORT', None) is None: # only thing that can get past post_save in the views raise DeisException( 'PORT needs to be set in the config ' 'when using a private registry') def set_tags(self, previous_config): """verify the tags exist on any nodes as labels""" if not self.tags: return # Get all nodes with label selectors nodes = self._scheduler.get_nodes(labels=self.tags).json() if nodes['items']: return labels = ['{}={}'.format(key, value) for key, value in self.tags.items()] message = 'No nodes matched the provided labels: {}'.format(', '.join(labels)) # Find out if there are any other tags around old_tags = getattr(previous_config, 'tags') if old_tags: old = ['{}={}'.format(key, value) for key, value in old_tags.items()] new = set(labels) - set(old) if new: message += ' - Addition of {} is the cause'.format(', '.join(new)) raise DeisException(message) def save(self, **kwargs): """merge the old config with the new""" try: # Get config from the latest available release try: previous_config = self.app.release_set.latest().config except Release.DoesNotExist: # If that doesn't exist then fallback on app config # usually means a totally new app previous_config = self.app.config_set.latest() for attr in ['cpu', 'memory', 'tags', 'registry', 'values', 'healthcheck']: data = getattr(previous_config, attr, {}).copy() new_data = getattr(self, attr, {}).copy() # remove config keys if a null value is provided for key, value in new_data.items(): if value is None: # error if unsetting non-existing key if key not in data: raise UnprocessableEntity('{} does not exist under {}'.format(key, attr)) # noqa data.pop(key) else: data[key] = value setattr(self, attr, data) self._migrate_legacy_healthcheck() self.set_registry() self.set_tags(previous_config) except Config.DoesNotExist: pass return super(Config, self).save(**kwargs)
class AuthToken(BaseSubUserModel): id: int = models.BigAutoField(primary_key=True, editable=False) usercomponent = models.ForeignKey("spider_base.UserComponent", on_delete=models.CASCADE, related_name="authtokens") attached_to_content = models.ForeignKey("spider_base.AssignedContent", on_delete=models.CASCADE, related_name="attached_tokens", null=True, blank=True) # -1=false,0=usercomponent,1-...=anchor persist: int = models.BigIntegerField(blank=True, default=-1, db_index=True) # brute force protection # 16 = id in hexadecimal # +2 for seperators # when swapping tokens the id in the token can missmatch # so don't rely on it token: str = models.CharField(max_length=MAX_TOKEN_B64_SIZE + hex_size_of_bigid + 2, db_index=True, unique=True, null=True, validators=[validator_token]) referrer = models.ForeignKey("spider_base.ReferrerObject", on_delete=models.CASCADE, related_name="tokens", blank=True, null=True) session_key: str = models.CharField(max_length=40, null=True) extra: dict = JSONField(default=dict, blank=True) created = models.DateTimeField(auto_now_add=True, editable=False) default_update_fields = None objects = AuthTokenManager() def __str__(self): return "{}...".format(self.token[:-_striptoken]) def initialize_token(self): self.token = create_b64_id_token(self.id, "_", getattr(settings, "TOKEN_SIZE", 30)) def save(self, **kwargs): # maybe a little overengineered for that a clash can only happen # if an old generated token has the same token as a new one # the used id switched from the one of a usercomponent to the one # of the token start_token_creation = not self.token created = not self.id if start_token_creation: if not created: update_fields = set( kwargs.pop("update_fields", self.default_update_fields)) update_fields.discard("token") kwargs["update_fields"] = update_fields super().save(**kwargs) for i in range(0, 1000): if i >= 999: # in reality this path will be very unlikely if created: self.delete() self.token = None raise TokenCreationError( 'A possible infinite loop was detected') self.initialize_token() try: with transaction.atomic(): super().save(update_fields=["token"], using=kwargs.get("using")) break except IntegrityError: pass else: super().save(**kwargs)
class Feed(models.Model): """Represents a single GTFS feed. This data is not part of the General Transit Feed Specification. It is used to allow storage of several GTFS feeds in the same database. """ name = models.CharField(max_length=255) created = models.DateTimeField(auto_now_add=True) meta = JSONField(default={}, blank=True, null=True) class Meta: db_table = 'gtfs_feed' app_label = 'multigtfs' def __str__(self): if self.name: return "%d %s" % (self.id, self.name) else: return "%d" % self.id def import_gtfs(self, gtfs_obj): """Import a GTFS file as feed Keyword arguments: gtfs_obj - A path to a zipped GTFS file, a path to an extracted GTFS file, or an open GTFS zip file. Returns is a list of objects imported """ total_start = time.time() # Determine the type of gtfs_obj opener = None filelist = None if isinstance(gtfs_obj, string_types) and os.path.isdir(gtfs_obj): opener = open filelist = [] for dirpath, dirnames, filenames in os.walk(gtfs_obj): filelist.extend([os.path.join(dirpath, f) for f in filenames]) else: zfile = ZipFile(gtfs_obj, 'r') opener = opener_from_zipfile(zfile) filelist = zfile.namelist() gtfs_order = ( Agency, Stop, Route, Service, ServiceDate, Trip, StopTime, Frequency, Fare, FareRule, Transfer, FeedInfo, ) post_save.disconnect(dispatch_uid='post_save_stop') try: for klass in gtfs_order: for f in filelist: if f.endswith(klass._filename): start_time = time. time() table = opener(f) count = klass.import_txt(table, self) or 0 end_time = time.time() logger.info( 'Imported %s (%d %s) in %0.1f seconds', klass._filename, count, klass._meta.verbose_name_plural, end_time - start_time) table.close() finally: post_save.connect(post_save_stop, sender=Stop) def export_gtfs(self, gtfs_file): """Export a GTFS file as feed Keyword arguments: gtfs_file - A path or file-like object for the GTFS feed This function will close the file in order to finalize it. """ total_start = time.time() z = open_writable_zipfile(gtfs_file) gtfs_order = ( Agency, Service, ServiceDate, Fare, FareRule, FeedInfo, Frequency, Route, StopTime, Stop, Transfer, Trip, ) for klass in gtfs_order: start_time = time.time() content = klass.export_txt(self) if content: z.writestr(klass._filename, content) end_time = time.time() record_count = content.count(type(content)('\n')) - 1 logger.info( 'Exported %s (%d %s) in %0.1f seconds', klass._filename, record_count, klass._meta.verbose_name_plural, end_time - start_time) z.close() total_end = time.time() logger.info( 'Export completed in %0.1f seconds.', total_end - total_start)
class Build(models.Model): """Build data.""" project = models.ForeignKey( Project, verbose_name=_('Project'), related_name='builds', on_delete=models.CASCADE, ) version = models.ForeignKey( Version, verbose_name=_('Version'), null=True, related_name='builds', on_delete=models.CASCADE, ) type = models.CharField( _('Type'), max_length=55, choices=BUILD_TYPES, default='html', ) state = models.CharField( _('State'), max_length=55, choices=BUILD_STATE, default='finished', ) date = models.DateTimeField(_('Date'), auto_now_add=True) success = models.BooleanField(_('Success'), default=True) setup = models.TextField(_('Setup'), null=True, blank=True) setup_error = models.TextField(_('Setup error'), null=True, blank=True) output = models.TextField(_('Output'), default='', blank=True) error = models.TextField(_('Error'), default='', blank=True) exit_code = models.IntegerField(_('Exit code'), null=True, blank=True) commit = models.CharField( _('Commit'), max_length=255, null=True, blank=True, ) _config = JSONField(_('Configuration used in the build'), default=dict) length = models.IntegerField(_('Build Length'), null=True, blank=True) builder = models.CharField( _('Builder'), max_length=255, null=True, blank=True, ) cold_storage = models.NullBooleanField( _('Cold Storage'), help_text='Build steps stored outside the database.', ) # Managers objects = BuildManager.from_queryset(BuildQuerySet)() # Only include BRANCH, TAG, UNKNOWN type Version builds. internal = InternalBuildManager.from_queryset(BuildQuerySet)() # Only include EXTERNAL type Version builds. external = ExternalBuildManager.from_queryset(BuildQuerySet)() CONFIG_KEY = '__config' class Meta: ordering = ['-date'] get_latest_by = 'date' index_together = [['version', 'state', 'type']] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._config_changed = False @property def previous(self): """ Returns the previous build to the current one. Matching the project and version. """ date = self.date or timezone.now() if self.project is not None and self.version is not None: return (Build.objects.filter( project=self.project, version=self.version, date__lt=date, ).order_by('-date').first()) return None @property def config(self): """ Get the config used for this build. Since we are saving the config into the JSON field only when it differs from the previous one, this helper returns the correct JSON used in this Build object (it could be stored in this object or one of the previous ones). """ if self.CONFIG_KEY in self._config: return (Build.objects.only('_config').get( pk=self._config[self.CONFIG_KEY])._config) return self._config @config.setter def config(self, value): """ Set `_config` to value. `_config` should never be set directly from outside the class. """ self._config = value self._config_changed = True def save(self, *args, **kwargs): # noqa """ Save object. To save space on the db we only save the config if it's different from the previous one. If the config is the same, we save the pk of the object that has the **real** config under the `CONFIG_KEY` key. """ if self.pk is None or self._config_changed: previous = self.previous # yapf: disable if ( previous is not None and self._config and self._config == previous.config ): # yapf: enable previous_pk = previous._config.get(self.CONFIG_KEY, previous.pk) self._config = {self.CONFIG_KEY: previous_pk} super().save(*args, **kwargs) self._config_changed = False def __str__(self): return ugettext( 'Build {project} for {usernames} ({pk})'.format( project=self.project, usernames=' '.join( self.project.users.all().values_list('username', flat=True), ), pk=self.pk, ), ) def get_absolute_url(self): return reverse('builds_detail', args=[self.project.slug, self.pk]) def get_full_url(self): """ Get full url of the build including domain. Example: https://readthedocs.org/projects/pip/builds/99999999/ """ scheme = 'http' if settings.DEBUG else 'https' full_url = '{scheme}://{domain}{absolute_url}'.format( scheme=scheme, domain=settings.PRODUCTION_DOMAIN, absolute_url=self.get_absolute_url()) return full_url def get_commit_url(self): """Return the commit URL.""" repo_url = self.project.repo if self.is_external: if 'github' in repo_url: user, repo = get_github_username_repo(repo_url) if not user and not repo: return '' repo = repo.rstrip('/') return GITHUB_PULL_REQUEST_COMMIT_URL.format( user=user, repo=repo, number=self.version.verbose_name, commit=self.commit) if 'gitlab' in repo_url: user, repo = get_gitlab_username_repo(repo_url) if not user and not repo: return '' repo = repo.rstrip('/') return GITLAB_MERGE_REQUEST_COMMIT_URL.format( user=user, repo=repo, number=self.version.verbose_name, commit=self.commit) # TODO: Add External Version Commit URL for BitBucket. else: if 'github' in repo_url: user, repo = get_github_username_repo(repo_url) if not user and not repo: return '' repo = repo.rstrip('/') return GITHUB_COMMIT_URL.format(user=user, repo=repo, commit=self.commit) if 'gitlab' in repo_url: user, repo = get_gitlab_username_repo(repo_url) if not user and not repo: return '' repo = repo.rstrip('/') return GITLAB_COMMIT_URL.format(user=user, repo=repo, commit=self.commit) if 'bitbucket' in repo_url: user, repo = get_bitbucket_username_repo(repo_url) if not user and not repo: return '' repo = repo.rstrip('/') return BITBUCKET_COMMIT_URL.format(user=user, repo=repo, commit=self.commit) return None @property def finished(self): """Return if build has a finished state.""" return self.state == BUILD_STATE_FINISHED @property def is_stale(self): """Return if build state is triggered & date more than 5m ago.""" mins_ago = timezone.now() - datetime.timedelta(minutes=5) return self.state == BUILD_STATE_TRIGGERED and self.date < mins_ago @property def is_external(self): return self.version.type == EXTERNAL @property def external_version_name(self): if self.is_external: if self.project.git_provider_name == GITHUB_BRAND: return GITHUB_EXTERNAL_VERSION_NAME if self.project.git_provider_name == GITLAB_BRAND: return GITLAB_EXTERNAL_VERSION_NAME # TODO: Add External Version Name for BitBucket. return GENERIC_EXTERNAL_VERSION_NAME return None def using_latest_config(self): return int(self.config.get('version', '1')) == LATEST_CONFIGURATION_VERSION
class StopTime(Base): """A specific stop on a route on a trip. This implements stop_times.txt in the GTFS feed """ trip = models.ForeignKey(Trip, on_delete=models.CASCADE) stop = models.ForeignKey(Stop, on_delete=models.CASCADE) arrival_time = SecondsField( default=None, null=True, blank=True, help_text="Arrival time. Must be set for end stops of trip.") departure_time = SecondsField( default=None, null=True, blank=True, help_text='Departure time. Must be set for end stops of trip.') stop_sequence = models.IntegerField() stop_headsign = models.CharField( max_length=255, blank=True, help_text="Sign text that identifies the stop for passengers") pickup_type = models.CharField( max_length=1, blank=True, choices=(('0', 'Regularly scheduled pickup'), ('1', 'No pickup available'), ('2', 'Must phone agency to arrange pickup'), ('3', 'Must coordinate with driver to arrange pickup')), help_text="How passengers are picked up") drop_off_type = models.CharField( max_length=1, blank=True, choices=(('0', 'Regularly scheduled drop off'), ('1', 'No drop off available'), ('2', 'Must phone agency to arrange drop off'), ('3', 'Must coordinate with driver to arrange drop off')), help_text="How passengers are picked up") shape_dist_traveled = models.FloatField( "shape distance traveled", null=True, blank=True, help_text='Distance of stop from start of shape') extra_data = JSONField(default={}, blank=True, null=True) def __str__(self): return "%s-%s-%s" % (self.trip, self.stop.stop_id, self.stop_sequence) class Meta: db_table = 'gtfs_stop_time' app_label = 'multigtfs' _column_map = (('trip_id', 'trip__trip_id'), ('arrival_time', 'arrival_time'), ('departure_time', 'departure_time'), ('stop_id', 'stop__stop_id'), ('stop_sequence', 'stop_sequence'), ('stop_headsign', 'stop_headsign'), ('pickup_type', 'pickup_type'), ('drop_off_type', 'drop_off_type'), ('shape_dist_traveled', 'shape_dist_traveled')) _filename = 'stop_times.txt' _rel_to_feed = 'trip__route__feed' _sort_order = ('trip__trip_id', 'stop_sequence') _unique_fields = ('trip_id', 'stop_sequence')
class MsgLog(models.Model): authevent_id = models.IntegerField() receiver = models.CharField(max_length=255) msg = JSONField() created = models.DateTimeField(auto_now_add=True)
class UploadLayer(models.Model): """Layers stored in an uploaded data set. """ upload = models.ForeignKey(UploadedData, null=True, blank=True) upload_file = models.ForeignKey(UploadFile, null=True, blank=True) index = models.IntegerField(default=0) # *deprecated* name of the layer, sometimes other data name = models.CharField(max_length=64, null=True) fields = JSONField(null=True, default={}) content_type = models.ForeignKey(ContentType, blank=True, null=True) object_id = models.PositiveIntegerField(blank=True, null=True) layer = GenericForeignKey('content_type', 'object_id') configuration_options = JSONField(null=True) import_status = models.CharField(max_length=15, blank=True, null=True) task_id = models.CharField(max_length=36, blank=True, null=True) feature_count = models.IntegerField(null=True, blank=True) # Name of the layer as known in the file/package/endpoint it came from. internal_layer_name = models.CharField(max_length=64, null=True) # Geonode-wide unique name for layer. layer_name = models.CharField(max_length=64, null=True) layer_type = models.CharField(max_length=10, null=True) @property def file_name(self): if not self.upload_file: return None return self.upload_file.name @property def file_type(self): """A layer's 'file type' - really the file type of the file it is in. """ if not self.upload_file: return None return self.upload_file.file_type @property def layer_data(self): """ Serialized information about the layer. """ if not self.layer: return return {'title': self.layer.title, 'url': self.layer.get_absolute_url(), 'id': self.layer.id} @property def description(self): """ Serialized description of the layer. """ params = dict(name=self.name, fields=self.fields, imported_layer=None, index=self.index, id=self.id) if self.layer: params['imported_layer'] = {'typename': self.layer.typename, 'name': self.layer.name, 'url': self.layer.get_absolute_url()} return params @property def status(self): """ Returns the status of the import of this UploadedLayer. """ s = 'UNKNOWN' if self.import_status is None else self.import_status return s @property def import_full_error(self): """ Returns the last error created for an import task """ try: ue = UploadException.objects.get(task_id=self.task_id) except: ue = None return ue @property def import_error(self): if self.import_full_error is None: return None e = str(self.import_full_error) if re.search('Runtime Error: ', e): e = e.split('Runtime Error: ')[1] if re.search('layer creation option', e): return 'Layer with this name already imported.' if re.search('duplicate key', e): m = re.search('Key \(([^)]+)\)', e) if m: col = m.group(1) return 'Duplicate key found in column %s' % (col) return e class Meta: ordering = ('index',)
class AbstractTemplate(ShareableOrgMixinUniqueName, BaseConfig): """ Abstract model implementing a netjsonconfig template """ tags = TaggableManager( through=get_model_name('config', 'TaggedTemplate'), blank=True, help_text=_('A comma-separated list of template tags, may be used ' 'to ease auto configuration with specific settings (eg: ' '4G, mesh, WDS, VPN, ecc.)'), ) vpn = models.ForeignKey( get_model_name('config', 'Vpn'), verbose_name=_('VPN'), blank=True, null=True, on_delete=models.CASCADE, ) type = models.CharField( _('type'), max_length=16, choices=TYPE_CHOICES, default='generic', db_index=True, help_text=_('template type, determines which features are available'), ) default = models.BooleanField( _('enabled by default'), default=False, db_index=True, help_text= _('whether new configurations will have this template enabled by default' ), ) required = models.BooleanField( _('required'), default=False, db_index=True, help_text= _('if checked, will force the assignment of this template to all the ' 'devices of the organization (if no organization is selected, it will ' 'be required for every device in the system)'), ) auto_cert = models.BooleanField( _('auto certificate'), default=default_auto_cert, db_index=True, help_text=_('whether x509 client certificates should ' 'be automatically managed behind the scenes ' 'for each configuration using this template, ' 'valid only for the VPN type'), ) default_values = JSONField( _('Default Values'), default=dict, blank=True, help_text=_('A dictionary containing the default ' 'values for the variables used by this ' 'template; these default variables will ' 'be used during schema validation.'), load_kwargs={'object_pairs_hook': OrderedDict}, dump_kwargs={'indent': 4}, ) __template__ = True class Meta: abstract = True verbose_name = _('template') verbose_name_plural = _('templates') unique_together = (('organization', 'name'), ) def save(self, *args, **kwargs): """ modifies status of related configs if key attributes have changed (queries the database) """ update_related_config_status = False if not self._state.adding: current = self.__class__.objects.get(pk=self.pk) for attr in ['backend', 'config']: if getattr(self, attr) != getattr(current, attr): update_related_config_status = True break # save current changes super().save(*args, **kwargs) # update relations if update_related_config_status: transaction.on_commit( lambda: update_template_related_config_status.delay(self.pk)) def _update_related_config_status(self): # use atomic to ensure any code bound to # be executed via transaction.on_commit # is executed after the whole block with transaction.atomic(): changing_status = list( self.config_relations.exclude(status='modified').values_list( 'pk', flat=True)) for config in self.config_relations.select_related( 'device').iterator(): # config modified signal sent regardless config._send_config_modified_signal( action='related_template_changed') # config status changed signal sent only if status changed if config.pk in changing_status: config._send_config_status_changed_signal() self.config_relations.exclude(status='modified').update( status='modified') def clean(self, *args, **kwargs): """ * validates org relationship of VPN if present * validates default_values field * ensures VPN is selected if type is VPN * clears VPN specific fields if type is not VPN * automatically determines configuration if necessary * if flagged as required forces it also to be default """ self._validate_org_relation('vpn') if not self.default_values: self.default_values = {} if not isinstance(self.default_values, dict): raise ValidationError({ 'default_values': _('the supplied value is not a JSON object') }) if self.type == 'vpn' and not self.vpn: raise ValidationError({ 'vpn': _('A VPN must be selected when template type is "VPN"') }) elif self.type != 'vpn': self.vpn = None self.auto_cert = False if self.type == 'vpn' and not self.config: self.config = self.vpn.auto_client(auto_cert=self.auto_cert) if self.required and not self.default: self.default = True super().clean(*args, **kwargs) if not self.config: raise ValidationError( _('The configuration field cannot be empty.')) def get_context(self, system=False): context = {} if self.default_values and not system: context = copy(self.default_values) context.update(super().get_context()) return context def get_system_context(self): system_context = self.get_context(system=True) return OrderedDict(sorted(system_context.items())) def clone(self, user): clone = copy(self) clone.name = self.__get_clone_name() clone._state.adding = True clone.pk = None # avoid cloned templates to be flagged as default # to avoid potential unwanted duplications in # newly registrated devices clone.default = False clone.full_clean() clone.save() ct = ContentType.objects.get(model='template') LogEntry.objects.log_action( user_id=user.id, content_type_id=ct.pk, object_id=clone.pk, object_repr=clone.name, action_flag=ADDITION, ) return clone def __get_clone_name(self): name = '{} (Clone)'.format(self.name) index = 2 while self.__class__.objects.filter(name=name).count(): name = '{} (Clone {})'.format(self.name, index) index += 1 return name
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={}) 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): return sum([stock.quantity 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_product_discounts(self, discounts, **kwargs)) if discounts: modifier = max(discounts) price += modifier 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_item.quantity > 0 for stock_item 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): # By default selects stock with lowest cost price stock = sorted(self.stock.all(), 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
class JSONFieldTestModel(models.Model): json = JSONField(u"test", null=True, blank=True) class Meta: app_label = 'jsonfield'
class GrangerCausalityConn(models.Model): imports = JSONField() def __unicode__(self): return str(self.imports)
class User(AbstractUser): email = models.EmailField('email address', unique=True) is_active = models.BooleanField( _('Valid'), default=True, help_text=_( 'Designates this as a valid user account (eg. not spammer or fake). ' 'Un-checking this will prevent user from being able to login and ' 'is preferable to deleting accounts.'), ) is_osedev = models.BooleanField( _('OSE Developer'), default=False, help_text=_('Designates whether the user has been approved as an ' 'OSE Developer at some point in time. Adds their logged ' 'time to global development effort.'), ) is_current = models.BooleanField( _('Current'), default=False, help_text=_('Requires that OSE Developer status is also checked. ' 'Active OSE Developer is someone who is currently an ' 'active contributor to OSE.'), ) location = models.CharField( _('Location'), max_length=512, blank=True, help_text= _('Used with Google Maps API to get a lat/long for mapping contributors.' ), ) location_details = JSONField(null=True) latitude = models.DecimalField(_("Latitude"), null=True, max_digits=9, decimal_places=6) longitude = models.DecimalField(_("Longitude"), null=True, max_digits=9, decimal_places=6) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__original_location = self.location def __str__(self): return "{} ({})".format(self.get_full_name(), self.get_username()) def clean(self): if self.is_current and not self.is_osedev: raise ValidationError({ 'is_current': _("To enable current status a user must also be an OSE Developer." ) }) if not self.location: self.latitude = None self.longitude = None self.location_details = None elif settings.GEOCODE_ENABLED and settings.GOOGLE_API_KEY: if not self.__original_location or (self.__original_location != self.location): gmaps = googlemaps.Client(key=settings.GOOGLE_API_KEY) self.location_details = gmaps.geocode(self.location) if self.location_details: self.latitude = self.location_details[0]['geometry'][ 'location']['lng'] self.longitude = self.location_details[0]['geometry'][ 'location']['lat']
class GrangerRan(models.Model): date = models.CharField(max_length=100) imports = JSONField() def __unicode__(self): return str(self.date)
class Site(models.Model): identifier = models.CharField("ID", max_length=255) name = models.CharField(db_index=True, max_length=255) type = models.ForeignKey(SiteType, verbose_name='Type of Site', related_name="sites", null=True, blank=True, on_delete=models.SET_NULL) phone = models.CharField(max_length=255, blank=True, null=True) address = models.TextField(blank=True, null=True) public_desc = models.TextField("Public Description", blank=True, null=True) additional_desc = models.TextField("Additional Description", blank=True, null=True) project = models.ForeignKey(Project, related_name='sites') logo = models.ImageField(upload_to="logo", default="logo/default_site_image.png", max_length=500) is_active = models.BooleanField(db_index=True, default=True) location = PointField(geography=True, srid=4326, blank=True, null=True) is_survey = models.BooleanField(default=False) date_created = models.DateTimeField(auto_now_add=True, blank=True) date_modified = models.DateTimeField(auto_now=True, blank=True) region = models.ForeignKey(Region, related_name='regions', blank=True, null=True) site_meta_attributes_ans = JSONField(default=dict) all_ma_ans = JSONField(default=dict) site_featured_images = JSONField(default=dict) current_progress = models.FloatField(default=0.0) current_status = models.IntegerField(default=0) enable_subsites = models.BooleanField(default=False) site = models.ForeignKey('self', blank=True, null=True, related_name="sub_sites") weight = models.IntegerField(default=0, blank=True) all_objects = SiteAllManager() objects = SiteManager() logs = GenericRelation('eventlog.FieldSightLog') class Meta: ordering = ['-is_active', '-id'] unique_together = [ ('identifier', 'project', 'is_active'), ] @property def latitude(self): if self.location: return self.location.y @property def longitude(self): if self.location: return self.location.x def update_status(self): try: status = self.site_instances.order_by('-date').first().form_status except: status = 0 self.current_status = status self.save() def getname(self): return self.name def __unicode__(self): return u'{}'.format(self.name) @property def get_supervisors(self): return self.site_roles.all() @property def get_supervisor_id(self): staffs = list(self.site_roles.filter(group__name="Site Supervisor")) if staffs: return [role.user.id for role in staffs] return [] def get_organization_name(self): return self.project.organization.name def get_project_name(self): return self.project.name def get_site_type(self): return self.type.name def update_current_progress(self): if not self.enable_subsites: from onadata.apps.fieldsight.tasks import update_current_progress_site update_current_progress_site.apply_async( kwargs={'site_id': self.id}, countdown=5) def progress(self): from onadata.apps.fieldsight.utils.progress import default_progress return default_progress(self, self.project) @property def site_progress(self): return self.progress() @property def status(self): if self.site_instances.filter(form_status=1).count(): return 1 elif self.site_instances.filter(form_status=2).count(): return 2 elif self.site_instances.filter(form_status=0).count(): return 0 elif self.site_instances.filter(form_status=3).count(): return 3 return 4 @property def site_status(self): forms = self.project.project_forms.filter(is_staged=True, is_deleted=False, is_deployed=True) try: return self.site_instances.filter(project_fxf__in=forms).order_by( '-instance_id')[0].get_abr_form_status() except: return "No Submission" def get_site_submission(self): instances = self.site_instances.all().order_by('-date') outstanding, flagged, approved, rejected = [], [], [], [] for submission in instances: if submission.form_status == 0: outstanding.append(submission) elif submission.form_status == 1: rejected.append(submission) elif submission.form_status == 2: flagged.append(submission) elif submission.form_status == 3: approved.append(submission) return outstanding, flagged, approved, rejected def get_site_submission_count(self): instances = self.site_instances.all().order_by('-date') outstanding, flagged, approved, rejected = 0, 0, 0, 0 for submission in instances: if submission.form_status == 0: outstanding += 1 elif submission.form_status == 1: rejected += 1 elif submission.form_status == 2: flagged += 1 elif submission.form_status == 3: approved += 1 response = {} response['outstanding'] = outstanding response['rejected'] = rejected response['flagged'] = flagged response['approved'] = approved return response def get_parent_sites(self): site = self site_ids = Site.objects.select_related('site').filter( id=site.id).values('id', 'site', 'site__site') parent_sites = [] parent_sites.extend([ site_ids[0]['id'], site_ids[0]['site'], site_ids[0]['site__site'] ]) return parent_sites def get_absolute_url(self): return "/fieldsight/application/#/site-dashboard/{}".format(self.pk)
class BaseOrder(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) name = models.CharField(max_length=100, verbose_name='상품명') amount = models.PositiveIntegerField(verbose_name='결제금액') merchant_uid = models.UUIDField(default=uuid4, editable=False) imp_uid = models.CharField(max_length=100, blank=True) meta = JSONField(blank=True, default={}) status = models.CharField(max_length=9, choices=( ('ready', '미결제'), ('paid', '결제완료'), ('cancelled', '결제취소'), ('failed', '결제실패'), ), default='ready', db_index=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) is_ready = property(lambda self: self.status == 'ready') is_paid = property(lambda self: self.status == 'paid') is_paid_ok = property(lambda self: self.status == 'paid' and self.amount == self.meta.get('amount')) is_cancelled = property(lambda self: self.status == 'cancelled') is_failed = property(lambda self: self.status == 'failed') receipt_url = named_property('영수증')( lambda self: self.meta.get('receipt_url')) cancel_reason = named_property('취소이유')( lambda self: self.meta.get('cancel_reason')) fail_reason = named_property('실패이유')( lambda self: self.meta.get('fail_reason', '')) paid_at = named_property('결제일시')( lambda self: timestamp_to_datetime(self.meta.get('paid_at'))) failed_at = named_property('실패일시')( lambda self: timestamp_to_datetime(self.meta.get('failed_at'))) cancelled_at = named_property('취소일시')( lambda self: timestamp_to_datetime(self.meta.get('cancelled_at'))) class Meta: ordering = ('-id', ) @named_property('결제금액') def amount_html(self): return mark_safe('<div style="float: right;">{0}</div>'.format( intcomma(self.amount))) @named_property('처리결과') def status_html(self): cls, text_color = '', '' help_text = '' if self.is_ready: cls, text_color = 'fa fa-shopping-cart', '#ccc' elif self.is_paid_ok: cls, text_color = 'fa fa-check-circle', 'green' elif self.is_cancelled: cls, text_color = 'fa fa-times', 'gray' help_text = self.cancel_reason elif self.is_failed: cls, text_color = 'fa fa-ban', 'red' help_text = self.fail_reason html = ''' <span style="color: {text_color};" title="this is title"> <i class="{class_names}"></i> {label} </span>'''.format(class_names=cls, text_color=text_color, label=self.get_status_display()) if help_text: html += '<br/>' + help_text return mark_safe(html) @named_property('영수증 링크') def receipt_link(self): if self.is_paid_ok and self.receipt_url: return mark_safe('<a href="{0}" target="_blank">영수증</a>'.format( self.receipt_url)) @property def api(self): 'Iamport Client 인스턴스' return Iamport(settings.IAMPORT_API_KEY, settings.IAMPORT_API_SECRET) def update(self, commit=True, meta=None): '결재내역 갱신' if self.imp_uid: #self.meta = meta or self.api.find(imp_uid=self.imp_uid) try: self.meta = meta or self.api.find(imp_uid=self.imp_uid) except Iamport.HttpError: raise Http404('Not found {}'.format(self.imp_uid)) # merchant_uid는 반드시 매칭되어야 합니다. assert str(self.merchant_uid) == self.meta['merchant_uid'] self.status = self.meta['status'] if commit: self.save() def cancel(self, reason=None, commit=True): '결제내역 취소' try: meta = self.api.cancel(reason, imp_uid=self.imp_uid) assert str(self.merchant_uid) == self.meta['merchant_uid'] self.update(commit=commit, meta=meta) except Iamport.ResponseError as e: # 취소시 오류 예외처리(이미 취소된 결제는 에러가 발생함) self.update(commit=commit) if commit: self.save()
class MapStoreData(models.Model): blob = JSONField(null=False, default={}) resource = models.ForeignKey(MapStoreResource, null=False, blank=False, on_delete=models.CASCADE)
class TableSettings(models.Model): user = models.ForeignKey(User) name = models.CharField(max_length=128) value = JSONField() per_page = models.IntegerField()
class Translation(DirtyFieldsMixin, models.Model): entity = models.ForeignKey(Entity) locale = models.ForeignKey(Locale) user = models.ForeignKey(User, null=True, blank=True) string = models.TextField() # 0=zero, 1=one, 2=two, 3=few, 4=many, 5=other, null=no plural forms plural_form = models.SmallIntegerField(null=True, blank=True) date = models.DateTimeField(auto_now_add=True) approved = models.BooleanField(default=False) approved_user = models.ForeignKey(User, related_name='approvers', null=True, blank=True) approved_date = models.DateTimeField(null=True, blank=True) fuzzy = models.BooleanField(default=False) deleted = models.DateTimeField(default=None, null=True) # extra stores data that we want to save for the specific format # this translation is stored in, but that we otherwise don't care # about. extra = JSONField(default=extra_default) # Due to https://code.djangoproject.com/ticket/14891, # TranslationManager will be used for the reverse FK from entities, # e.g. entity.translation_set. Deleted translations cannot be # accessed via that relation. objects = TranslationManager() deleted_objects = DeletedTranslationManager() def __unicode__(self): return self.string def save(self, imported=False, *args, **kwargs): super(Translation, self).save(*args, **kwargs) # Only one translation can be approved at a time for any # Entity/Locale. if self.approved: (Translation.objects.filter( entity=self.entity, locale=self.locale, plural_form=self.plural_form).exclude(pk=self.pk).update( approved=False, approved_user=None, approved_date=None)) if not imported: # Update stats AFTER changing approval status. update_stats(self.entity.resource, self.locale) # Whenever a translation changes, mark the entity as having # changed in the appropriate locale. We could be smarter about # this but for now this is fine. self.entity.mark_changed(self.locale) def mark_for_deletion(self): self.deleted = timezone.now() self.save() def delete(self, stats=True, *args, **kwargs): super(Translation, self).delete(*args, **kwargs) if stats: update_stats(self.entity.resource, self.locale) def serialize(self): return { 'pk': self.pk, 'string': self.string, 'approved': self.approved, 'fuzzy': self.fuzzy, }
class Country(models.Model): code = models.CharField(max_length=2, unique=True) alpha_3_code = models.CharField(max_length=3, blank=True, default="") name = models.CharField(max_length=75) region = models.ForeignKey(Region, null=True, blank=True, related_name="countries") population = models.IntegerField(null=True, blank=True) primary_networks = models.ManyToManyField( Network, blank=True, db_table='uw_country_primary_networks') extra_data = JSONField(blank=True) tracker = FieldTracker() class Meta: db_table = 'uw_country' def gateway_language(self): if not hasattr(self, "_gateway_language"): data = self.extra_data if not isinstance(data, dict): data = {} self._gateway_language = next( iter(Language.objects.filter( code=data.get("gateway_language"))), None) return self._gateway_language def gateway_languages(self, with_primary=True): gl = self.gateway_language() if gl: ogls = [gl] else: ogls = [] for lang in self.language_set.all(): if lang.gateway_flag and lang not in ogls: ogls.append(lang) elif lang.gateway_language and lang.gateway_language not in ogls: ogls.append(lang.gateway_language) if not with_primary and gl: ogls.remove(gl) return ogls @classmethod def regions(cls): qs = cls.objects.all().values_list("region", flat=True).distinct() qs = qs.order_by("region.name") return qs @classmethod def gateway_data(cls): with_gateways = cls.objects.filter( language__gateway_language__isnull=False).distinct() without_gateways = cls.objects.exclude(pk__in=with_gateways) data = { x.code: { "obj": x, "gateways": defaultdict(lambda: []) } for x in with_gateways } data.update({ x.code: { "obj": x, "gateways": { "n/a": list(x.language_set.all()) } } for x in without_gateways }) for country in with_gateways: for lang in country.language_set.all(): if lang.gateway_language: data[country.code]["gateways"][ lang.gateway_language.code].append(lang) else: data[country.code]["gateways"]["n/a"].append(lang) return data def __str__(self): return self.name