Ejemplo n.º 1
0
class StyleAlternateName(models.Model):
    name = CITextField(unique=True)
    style = models.ForeignKey(
        Style, models.CASCADE, related_name='alternate_names')

    def __str__(self):
        return self.name
Ejemplo n.º 2
0
class ManufacturerAlternateName(models.Model):
    manufacturer = models.ForeignKey(
        Manufacturer, models.CASCADE, related_name='alternate_names')
    name = CITextField()

    def __str__(self):
        return f'{self.name} for {self.manufacturer_id}'
Ejemplo n.º 3
0
class PresetItem(Model):
    """ Preset food item to help user get started with adding food products. """

    name = CITextField(max_length=256, unique=True)
    measure = IntegerField(choices=UnitEnum.choices, null=True)

    def __str__(self):
        return f"{self.name} (preset)"
Ejemplo n.º 4
0
class BeerAlternateName(models.Model):
    beer = models.ForeignKey(Beer,
                             models.CASCADE,
                             related_name="alternate_names")
    name = CITextField()

    def __str__(self):
        return f"{self.name} for {self.beer_id}"
Ejemplo n.º 5
0
class Ratable(models.Model):  # type: ignore[disallow_any_explicit] # noqa F821
    id = models.UUIDField(default=generate_ulid_as_uuid, primary_key=True)
    description = CITextField()
    level = models.CharField(max_length=255, blank=True, null=True)
    inserted_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True
Ejemplo n.º 6
0
class StyleAlternateName(models.Model):
    name = CITextField()
    style = models.ForeignKey(Style, models.CASCADE, related_name="alternate_names")

    def __str__(self):  # pylint: disable=invalid-str-returned
        return self.name

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["name"], name="unique_alt_name_name")
        ]
Ejemplo n.º 7
0
class VenueAPIConfiguration(models.Model):
    venue = models.OneToOneField(
        Venue,
        models.CASCADE,
        related_name="api_configuration",
    )
    url = models.URLField(blank=True, null=True)
    api_key = models.CharField(max_length=100, blank=True)
    digital_pour_venue_id = models.CharField(max_length=50, blank=True)
    digital_pour_location_number = models.PositiveSmallIntegerField(
        blank=True,
        null=True,
    )
    untappd_location = models.PositiveIntegerField(blank=True, null=True)
    untappd_theme = models.PositiveIntegerField(blank=True, null=True)
    untappd_categories = ArrayField(
        models.CharField(max_length=50),
        default=list,
        blank=True,
        null=True,
    )
    taphunter_location = models.CharField(max_length=50, blank=True)
    taphunter_excluded_lists = ArrayField(
        models.CharField(max_length=50),
        default=list,
        blank=True,
        null=True,
    )
    taplist_io_display_id = models.CharField(max_length=50, blank=True)
    taplist_io_access_code = models.CharField(max_length=50, blank=True)
    beermenus_categories = ArrayField(
        models.TextField(),
        default=list,
        blank=True,
        null=True,
    )
    beermenus_slug = models.CharField(max_length=250, blank=True)
    arryved_location_id = models.CharField(max_length=50, blank=True)
    arryved_menu_id = models.CharField(max_length=50, blank=True)
    arryved_manufacturer_name = CITextField(blank=True)
    arryved_serving_sizes = ArrayField(
        models.TextField(),
        default=list,
        blank=True,
        null=True,
        help_text=_("Short codes for serving sizes of draft pours"),
    )
    arryved_pos_menu_names = ArrayField(
        models.TextField(),
        default=list,
        blank=True,
        null=True,
        help_text=_("Individual menus to process from the Arryved POS"),
    )
Ejemplo n.º 8
0
class Url(models.Model):
    user = models.ForeignKey(User,
                             on_delete=models.CASCADE,
                             related_name='url',
                             blank=True)
    alias = CITextField(max_length=25, blank=True, unique=True)
    link = models.URLField(max_length=1000)
    hit = models.IntegerField(default=0)

    def get_absolute_url(self):
        return reverse('myapp:home')

    def __str__(self):
        return str(self.alias) + " " + str(self.link)
Ejemplo n.º 9
0
class Style(models.Model):
    name = CITextField(unique=True)
    default_color = models.CharField(
        "HTML color (in hex) to use if the beer has no known color",
        max_length=9,  # #00112233 -> RGBA
        blank=True,
    )

    def merge_from(self, other_styles):
        alt_names = []
        with transaction.atomic():
            for style in other_styles:
                if style.id == self.id:
                    continue
                alt_names.append(style.name)
                style.beers.all().update(style=self)
                style.alternate_names.all().update(style=self)
                style.delete()
            try:
                # need the second transaction so we can run a query in the
                # event this fails. Because we're doing a raise in the except
                # block, the outer transaction will still be aborted in case
                # of failure.
                with transaction.atomic():
                    StyleAlternateName.objects.bulk_create(
                        [
                            StyleAlternateName(
                                name=name,
                                style=self,
                            )
                            for name in alt_names
                        ]
                    )
            except IntegrityError as exc:
                existing_names = [
                    i.name
                    for i in StyleAlternateName.objects.filter(
                        name__in=alt_names,
                    ).exclude(
                        style=self,
                    )
                ]
                raise ValueError(
                    "These alternate names already exist: "
                    f'{", ".join(existing_names)}'
                ) from exc

    def __str__(self):  # pylint: disable=invalid-str-returned
        return self.name
Ejemplo n.º 10
0
class Unit(Model):
    """ Unit of measurement for each food product. """
    class Meta:
        constraints = [
            CheckConstraint(check=Q(convert__gt=0),
                            name="check_unit_convert_positive"),
        ]

    symbol = CITextField(max_length=64, unique=True)
    plural = CITextField(max_length=64, unique=True, null=True, blank=True)
    code = CITextField(max_length=64, unique=True, null=True, blank=True)
    measure = IntegerField(choices=UnitEnum.choices)
    convert = DecimalField(
        max_digits=MAX_DIGITS,
        decimal_places=DP_CONVERT,
        validators=(validate_gt_zero, ),
    )

    def __str__(self):
        return str(self.symbol)

    def display(self):
        return (self.code or self.plural
                or (self.symbol if self.symbol != "none" else ""))
Ejemplo n.º 11
0
class PKey(models.Model):
    """ 이전에 사용한 데이터를 저장하기 위해서 Key-Value 기반의
    단순 데이터를 저장한다.

    TroubleShooting #1. 2018.03.14

    테스트 수행 시 database 에 hstore 및 citext 자료형이 없다는
    오류가 발생한다. 아래의 SQL 문을 실행시켜야 한다.

    그리고 DB 테스트를 수행할 때, default 로 test 데이터베이스를
    삭제하므로 -k 옵션을 주어 keep 하도록 해야한다.

    CREATE EXTENSION hstore WITH SCHEMA public;
    CREATE EXTENSION citext WITH SCHEMA public;

    $ python manage.py test -k machina.tests

    """
    key = CITextField(blank=False, unique=True)
Ejemplo n.º 12
0
class User(AbstractBaseUser, PermissionsMixin):
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    email = CIEmailField(verbose_name="email", max_length=60, unique=True)
    username = CITextField(max_length=30, unique=True)
    first_name = models.CharField(max_length=64, null=True, blank=True)
    last_name = models.CharField(max_length=64, null=True, blank=True)
    date_joined = models.DateTimeField(verbose_name="date joined",
                                       auto_now_add=True)
    last_login = models.DateTimeField(verbose_name="last login", auto_now=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)

    objects = UserManager()

    def __str__(self):
        return self.email
Ejemplo n.º 13
0
class Resume(models.Model):  # type: ignore[disallow_any_explicit] # noqa F821
    class Meta:
        db_table = "resumes"

        indexes = [
            models.Index(fields=("user", ), name="resumes_user_id_index")
        ]

    id = models.UUIDField(default=generate_ulid_as_uuid, primary_key=True)
    title = CITextField()
    description = models.TextField(blank=True, null=True)
    inserted_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    user = models.ForeignKey(User,
                             models.DO_NOTHING,
                             db_constraint=False,
                             db_index=False)

    def __str__(self):
        return self.title
Ejemplo n.º 14
0
class Style(models.Model):
    name = CITextField(unique=True)

    def merge_from(self, other_styles):
        alt_names = []
        with transaction.atomic():
            for style in other_styles:
                if style.id == self.id:
                    continue
                alt_names.append(style.name)
                style.beers.all().update(style=self)
                style.alternate_names.all().update(style=self)
                style.delete()
            try:
                # need the second transaction so we can run a query in the
                # event this fails. Because we're doing a raise in the except
                # block, the outer transaction will still be aborted in case
                # of failure.
                with transaction.atomic():
                    StyleAlternateName.objects.bulk_create([
                        StyleAlternateName(
                            name=name,
                            style=self,
                        ) for name in alt_names
                    ])
            except IntegrityError:
                existing_names = [
                    i.name for i in StyleAlternateName.objects.filter(
                        name__in=alt_names,
                    ).exclude(
                        style=self,
                    )
                ]
                raise ValueError(
                    'These alternate names already exist: '
                    f'{", ".join(existing_names)}'
                )

    def __str__(self):
        return self.name
Ejemplo n.º 15
0
class ASActivity(ARModel):
    "All ActivityStreams activities goes here."
    data = JSONField()
    domain = CITextField()
    actor = models.ForeignKey(
        Account, on_delete=models.DO_NOTHING, related_name='activities', to_field='ap_id')
    recipients = ArrayField(models.TextField(), null=True)

    @property
    def asobject(self):
        try:
            return ASObject.objects.get(data__id=self.data["object"])
        except KeyError:
            raise ASObject.DoesNotExist

    def __str__(self):
        try:
            return self.data.get("type", "") + ": " + self.data["id"]
        except KeyError:
            return super().__str__()

    class Meta:
        verbose_name_plural = 'ASActivities'
Ejemplo n.º 16
0
class Test(models.Model):
    """
    See, `ref1`: 'https://stackoverfloaw.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers'

    A New Approach for Verifying URL Uniqueness in Web Crawlers, SPIRE, Pisa, October 19th, 2011
    http://homepages.dcc.ufmg.br/~nivio/cursos/ri15/transp/spire11.pdf

    There are some URL Styles.
    First, URL has a parameters
        http://
    Second, URL has only path

    Third, Two URLs have same path, but they use different parameters
        Category 1. http://test.com/test?param1=a&param2=b
        Category 2. http://test.com/test
                    param1=a&param2=b
                    or,
                    {'param1':'a', 'param2':'b'}
    Let's simulate the insert process to guess what will happen.

    Assumption 1. Just distinguish request as a
        If someone try to insert same request again and again

    Case Insensitive Text Field CITextField

    """

    """Trouble Shooting, Message " You are trying to add non-nullable field 'something'"
    모델을 수정 시 django 에서 추가된 필드에 대해 값을 입력해준다. 따라서,
    입력할 값이 지정되지 않았으므로 발생하는 에러이다. (합리적임) 
    default 값을 추가해준다.
    """

    """Tip, Set Create & updated date/time in your models: 
    See, https://www.djangorocks.com/snippets/set-created-updated-datetime-in-your-models.html
    
    Samples:
    <pre><code>
        class Blog(models.Model):
            title = models.CharField(max_length=100)
            added = models.DateTimeField(auto_now_add=True)
            updated = models.DateTimeField(auto_now=True)
    </code></pre>
    """

    # ::: Record Updated Information :::
    created = models.DateTimeField(auto_now_add=True)  # created at 180316T10:40:10
    modfied = models.DateTimeField(auto_now=True)  # updated at 180316T10:50:22

    # ::: Request Data :::
    req_header = HStoreField()  # dictionary types
    method = models.TextField(default='')  # ex, PUT, POST, GET, HEAD, OPTIONS, ...

    # Notes // full_url's max_length option follows RFC 2616 - section 3.2.1 , RFC 7230, See `ref1`
    full_url = CITextField() # ex. http://test.com/path/for/url?param1&param2
    url = CITextField()  # ex. /path/for/url?param1&param2
    url_param = HStoreField(null=True)  # param1: value1, param2: value2
    body_param = HStoreField(null=True)  # body_param1: value1, ...

    # ::: Client Information :::
    # Notes // IPAddressField is deprecated since django 1.7 release
    client_ip = models.GenericIPAddressField(null=True)  # client ip,
    client_port = models.IntegerField(null=True, blank=True)
    client_process = CITextField(blank=True, null=True)  # like, chrome/12131 {process_name}/{pid}

    # ::: Server Information :::
    hostname = CITextField(blank=True) # www.google.com
    server_ip = models.GenericIPAddressField(null=True)
    server_port = models.IntegerField(null=True, blank=True)

    # ::: Response Data :::
    # Field for ResponseHeader
    res_code = models.IntegerField(blank=True, default=-1, null=True)
    res_header = HStoreField()

    # ::: User Interaction :::
    # user can update commentary to notify information
    comment = models.TextField(default="", blank=True)

    # ::: The section of boolean field :::
    is_https = models.BooleanField(default=False)
    has_body = models.BooleanField(default=False)

    # ::: Unique Url Checker
    url_key = models.TextField(null=True)
    param_hash_key = models.TextField(unique=True, null=True)
    param_key = models.TextField(unique=True, null=True)

    class Meta:
        ordering = ('created', )
Ejemplo n.º 17
0
class Manufacturer(models.Model):
    name = CITextField()
    url = models.URLField(blank=True)
    location = models.CharField(blank=True, max_length=50)
    logo_url = models.URLField(blank=True)
    facebook_url = models.URLField(blank=True)
    twitter_handle = models.CharField(max_length=50, blank=True)
    instagram_handle = models.CharField(max_length=50, blank=True)
    untappd_url = models.URLField(blank=True, null=True)
    automatic_updates_blocked = models.BooleanField(null=True, default=False)
    taphunter_url = models.URLField(blank=True, null=True)
    taplist_io_pk = models.PositiveIntegerField(blank=True, null=True)
    time_first_seen = models.DateTimeField(blank=True, null=True, default=now)
    beermenus_slug = models.CharField(max_length=250, blank=True, null=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["name"], name="unique_mfg_name"),
            models.UniqueConstraint(fields=["taplist_io_pk"],
                                    name="unique_mfg_taplist_io_pk"),
            models.UniqueConstraint(fields=["taphunter_url"],
                                    name="unique_mfg_taphunter_url"),
            models.UniqueConstraint(fields=["untappd_url"],
                                    name="unique_mfg_untappd_url"),
            models.UniqueConstraint(fields=["beermenus_slug"],
                                    name="unique_mfg_beermenus_slug"),
        ]

    def save(self, *args, **kwargs):
        if not self.beermenus_slug:
            self.beermenus_slug = None
        if not self.taphunter_url:
            self.taphunter_url = None
        if not self.untappd_url:
            self.untappd_url = None
        return super().save(*args, **kwargs)

    def merge_from(self, other):
        """Merge the data from other into self"""
        LOG.info("merging %s into %s", other, self)
        with transaction.atomic():
            other_beers = list(other.beers.all())
            my_beers = {i.name.casefold(): i for i in self.beers.all()}
            for beer in other_beers:
                beer.manufacturer = self
                if beer.name.casefold() in my_beers:
                    # we have a duplicate beer. Merge those two first.
                    # merge_from takes care of saving my_beer and deleting
                    # beer
                    # keep the one that was already present
                    my_beer = my_beers[beer.name.casefold()]
                    my_beer.merge_from(beer)
                else:
                    # good
                    beer.save()

            ManufacturerAlternateName.objects.filter(
                manufacturer=other, ).update(manufacturer=self)
            excluded_fields = {
                "name",
                "automatic_updates_blocked",
                "id",
                "time_first_seen",
            }
            for field in self._meta.fields:
                field_name = field.name
                if field_name in excluded_fields:
                    continue
                other_value = getattr(other, field_name)
                if getattr(self, field_name) or not other_value:
                    # don't overwrite data that's already there
                    # or isn't set in the other one
                    continue
                setattr(self, field_name, other_value)
            self.automatic_updates_blocked = True
            ManufacturerAlternateName.objects.update_or_create(
                name=other.name,
                manufacturer=self,
            )
            other.delete()
            if other.time_first_seen:
                if (not self.time_first_seen
                        or self.time_first_seen > other.time_first_seen):
                    self.time_first_seen = other.time_first_seen
            self.save()

    def __str__(self):  # pylint: disable=invalid-str-returned
        return self.name
Ejemplo n.º 18
0
class Beer(models.Model):
    name = CITextField()
    style = models.ForeignKey(
        Style,
        models.DO_NOTHING,
        related_name="beers",
        blank=True,
        null=True,
    )
    manufacturer = models.ForeignKey(
        Manufacturer,
        models.CASCADE,
        related_name="beers",
    )
    in_production = models.BooleanField(default=True)
    abv = models.DecimalField(
        "Alcohol content (% by volume)",
        max_digits=4,
        decimal_places=2,
        blank=True,
        null=True,
    )
    ibu = models.PositiveSmallIntegerField(
        "Bitterness (International Bitterness Units)",
        blank=True,
        null=True,
    )
    color_srm = models.DecimalField(
        "Color (Standard Reference Method)",
        max_digits=4,
        decimal_places=1,
        blank=True,
        null=True,
    )
    untappd_url = models.URLField("Untappd URL (if known)",
                                  blank=True,
                                  null=True)
    beer_advocate_url = models.URLField("BeerAdvocate URL (if known)",
                                        null=True,
                                        blank=True)
    rate_beer_url = models.URLField("RateBeer URL (if known)",
                                    blank=True,
                                    null=True)
    logo_url = models.URLField("Beer logo URL (if known)",
                               blank=True,
                               null=True)
    color_html = models.CharField(
        "HTML Color (in hex)",
        max_length=9,
        blank=True,  # #00112233 -> RGBA
    )
    api_vendor_style = models.CharField(
        "API vendor-provided style (hidden from API)",
        max_length=100,
        blank=True,
    )
    manufacturer_url = models.URLField(
        "Link to the beer on the manufacturer's website",
        blank=True,
        null=True,
    )
    automatic_updates_blocked = models.BooleanField(null=True, default=False)
    taphunter_url = models.URLField("TapHunter URL (if known)",
                                    blank=True,
                                    null=True)
    stem_and_stein_pk = models.PositiveIntegerField(blank=True, null=True)
    taplist_io_pk = models.PositiveIntegerField(blank=True, null=True)
    time_first_seen = models.DateTimeField(blank=True, null=True, default=now)
    tweeted_about = models.BooleanField(default=False)
    beermenus_slug = models.CharField(max_length=250, blank=True, null=True)

    class Meta:
        indexes = [
            models.Index(fields=["tweeted_about"]),
        ]
        constraints = [
            models.CheckConstraint(
                check=models.Q(abv__gte=0, abv__lte=100)
                | models.Q(abv__isnull=True),
                name="abv_positive",
            ),
            models.CheckConstraint(
                check=models.Q(ibu__lte=1000) | models.Q(ibu__isnull=True),
                name="ibu_not_unreal",
            ),
            models.CheckConstraint(
                check=models.Q(color_srm__lte=500, color_srm__gte=1)
                | models.Q(color_srm__isnull=True),
                name="srm_not_unrealistic",
            ),
            models.UniqueConstraint(
                fields=["beermenus_slug"],
                name="unique_beermenus_slug",
            ),
            models.UniqueConstraint(
                fields=["taplist_io_pk"],
                name="unique_taplist_io_pk",
            ),
            models.UniqueConstraint(
                fields=["stem_and_stein_pk"],
                name="unique_stem_and_stein_pk",
            ),
            models.UniqueConstraint(
                fields=["taphunter_url"],
                name="unique_taphunter_url",
            ),
            models.UniqueConstraint(
                fields=["manufacturer_url"],
                name="unique_manufacturer_url",
            ),
            models.UniqueConstraint(
                fields=["rate_beer_url"],
                name="unique_rate_beer_url",
            ),
            models.UniqueConstraint(
                fields=["untappd_url"],
                name="unique_untappd_url",
            ),
            models.UniqueConstraint(
                fields=["beer_advocate_url"],
                name="unique_beer_advocate_url",
            ),
            models.UniqueConstraint(
                fields=["name", "manufacturer"],
                name="unique_beer_per_manufacturer",
            ),
        ]

    def save(self, *args, **kwargs):
        # force empty IDs to null to avoid running afoul of unique constraints
        if not self.untappd_url:
            self.untappd_url = None
        if not self.beer_advocate_url:
            self.beer_advocate_url = None
        if not self.rate_beer_url:
            self.rate_beer_url = None
        if not self.manufacturer_url:
            self.manufacturer_url = None
        if not self.taphunter_url:
            self.taphunter_url = None
        if not self.beermenus_slug:
            self.beermenus_slug = None
        return super().save(*args, **kwargs)

    def __str__(self):  # pylint: disable=invalid-str-returned
        return self.name

    def render_srm(self):
        """Convert beer color in SRM into an HTML hex color"""
        if self.color_html:
            return self.color_html
        return render_srm(self.color_srm)

    def merge_from(self, other):
        LOG.info("merging %s into %s", other, self)
        with transaction.atomic():
            Tap.objects.filter(beer=other).update(beer=self)
            BeerAlternateName.objects.filter(beer=other).update(beer=self)
            try:
                with transaction.atomic():
                    BeerPrice.objects.filter(beer=other).update(beer=self)
            except IntegrityError:
                LOG.warning("Duplicate prices detected for %s", self)
                prices_updated = (BeerPrice.objects.filter(beer=other).exclude(
                    venue__in=models.Subquery(
                        BeerPrice.objects.filter(
                            beer=self).values("venue")), ).update(beer=self))
                prices_deleted = BeerPrice.objects.filter(beer=other).delete()
                LOG.info(
                    "Updated %s prices and deleted %s prices",
                    prices_updated,
                    prices_deleted,
                )
            excluded_fields = {
                "name",
                "in_production",
                "automatic_updates_blocked",
                "manufacturer",
                "id",
                "time_first_seen",
            }
            for field in self._meta.fields:
                field_name = field.name
                if field_name in excluded_fields:
                    continue
                other_value = getattr(other, field_name)
                if getattr(self, field_name) or not other_value:
                    # don't overwrite data that's already there
                    # or isn't set in the other one
                    continue
                setattr(self, field_name, other_value)
            self.automatic_updates_blocked = True
            if other.name != self.name:
                # this will only not happen if manufacturers aren't the same
                BeerAlternateName.objects.update_or_create(
                    name=other.name,
                    beer=self,
                )
            if other.time_first_seen:
                if (not self.time_first_seen
                        or self.time_first_seen > other.time_first_seen):
                    self.time_first_seen = other.time_first_seen
            other.delete()
            self.save()
Ejemplo n.º 19
0
class Account(AbstractBaseUser, PermissionsMixin, ARModel):
    "Customized model for a User (local or remote) or a Subforum"
    AS_TYPES = (
        ('Person', 'Person'),
        ('Service', 'Bot'),
        ('Group', 'Subforum'),
    )

    username = CITextField(unique=True, verbose_name='full username')
    ap_id = models.TextField(unique=True)
    inbox_uri = models.TextField()
    outbox_uri = models.TextField()
    url = models.TextField()
    is_locked = models.BooleanField(default=False)
    name = models.TextField(blank=True, default="", null=True)
    summary = models.TextField(blank=True, default="", null=True)
    type = models.CharField(choices=AS_TYPES, max_length=50, default="Person")
    is_superuser = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    email = CITextField(unique=True, null=True, blank=True)
    following = models.ManyToManyField('self',
                                       related_name='followers',
                                       symmetrical=False,
                                       through='Follow',
                                       through_fields=('followee', 'follower'))
    following_uri = models.TextField()
    followers_uri = models.TextField()
    public_key = models.TextField()
    private_key = models.TextField(null=True)
    followers_count = models.PositiveIntegerField(null=True)
    following_count = models.PositiveIntegerField(null=True)
    posts_count = models.PositiveIntegerField(null=True)

    USERNAME_FIELD = 'username'
    EMAIL_FIELD = 'email'
    REQUIRED_FIELDS = ['email']

    @property
    def preferred_username(self):
        username, _ = self.username.split("@")
        return username

    @property
    def domain(self):
        _, domain = self.username.split("@")
        return domain

    @property
    def is_local(self):
        return self.domain in settings.RBQ_LOCAL_DOMAINS

    @property
    def avatar(self):
        return ''

    @property
    def header(self):
        return ''

    @property
    def is_bot(self):
        return self.type == 'Service'

    @is_bot.setter
    def is_bot(self, val):
        if val:
            self.type = 'Service'
        elif self.type == 'Service':
            self.type = 'Person'

    objects = AccountManager()
    subforums = SubforumManager()
Ejemplo n.º 20
0
class Coupon(models.Model):
    value = models.IntegerField(_("Value"), help_text=_("Arbitrary coupon value"))
    code = CITextField(
        _("Code"), max_length=30, unique=True, blank=True,
        help_text=_("Leaving this field empty will generate a random code."))
    type = models.CharField(_("Type"), max_length=20, choices=COUPON_TYPES)
    user_limit = models.PositiveIntegerField(_("User limit"), default=1)
    limit_per_user = models.PositiveIntegerField(
        _("Coupon redeem limit per User"),
        default=1
    )
    created_at = models.DateTimeField(_("Created at"), auto_now_add=True)
    valid_from = models.DateTimeField(
        _("Valid from"), default=timezone.now,
        help_text=_("Defaults to start right away"))
    valid_until = models.DateTimeField(
        _("Valid until"), blank=True, null=True,
        help_text=_("Leave empty for coupons that never expire"))
    campaign = models.ForeignKey('Campaign', verbose_name=_("Campaign"), blank=True, null=True, related_name='coupons')

    objects = CouponManager()

    class Meta:
        ordering = ['created_at']
        verbose_name = _("Coupon")
        verbose_name_plural = _("Coupons")

    def __str__(self):
        return self.code

    def save(self, *args, **kwargs):
        if not self.code:
            self.code = Coupon.generate_code()
        super(Coupon, self).save(*args, **kwargs)

    def expired(self):
        return self.valid_until is not None and self.valid_until < timezone.now()

    @property
    def is_redeemed(self):
        """ Returns true is a coupon is redeemed (completely for all users) otherwise returns false. """
        fully_redeemed_users = [
            user for user in self.users.select_related('coupon').filter(
                last_redeemed_at__isnull=False
            ) if user.fully_redeemed
        ]
        return len(fully_redeemed_users) >= self.user_limit and self.user_limit is not 0

    @property
    def last_redeemed_at(self):
        coupon_user = self.users.filter(
            last_redeemed_at__isnull=False
        ).order_by('last_redeemed_at').last()

        if coupon_user:
            return coupon_user.last_redeemed_at

    @classmethod
    def generate_code(cls, prefix="", segmented=SEGMENTED_CODES):
        code = "".join(random.choice(CODE_CHARS) for i in range(CODE_LENGTH))
        if segmented:
            code = SEGMENT_SEPARATOR.join([code[i:i + SEGMENT_LENGTH] for i in range(0, len(code), SEGMENT_LENGTH)])
            return prefix + code
        else:
            return prefix + code

    def redeem(self, user=None):
        try:
            coupon_user = self.users.get(user=user)
        except CouponUser.DoesNotExist:
            try:  # silently fix unbouned or nulled coupon users
                coupon_user = self.users.get(user__isnull=True)
                coupon_user.user = user
            except CouponUser.DoesNotExist:
                coupon_user = CouponUser(coupon=self, user=user)
        coupon_user.last_redeemed_at = timezone.now()
        coupon_user.redeem_count += 1
        coupon_user.save()
        redeem_done.send(sender=self.__class__, coupon=self)
Ejemplo n.º 21
0
class Item(Model):
    """ A food product being tracked. """
    class Meta:
        constraints = [
            UniqueConstraint(fields=["user", "name"],
                             name="unique_item_user_name"),
            UniqueConstraint(fields=["user", "ident"],
                             name="unique_item_user_ident"),
            CheckConstraint(check=Q(minimum__gte=0),
                            name="check_item_minimum_not_negative"),
        ]

    name = CITextField(max_length=256)
    ident = TextField()
    user = ForeignKey(settings.AUTH_USER_MODEL,
                      related_name="items",
                      on_delete=CASCADE)
    unit = ForeignKey("Unit", on_delete=PROTECT, default=1)
    minimum = DecimalField(
        max_digits=MAX_DIGITS,
        decimal_places=DP_QUANTITY,
        default=0,
        validators=(MinValueValidator(0), ),
    )
    added = DateTimeField()

    def save(self,
             force_insert=False,
             force_update=False,
             using=None,
             update_fields=None):
        if not self.id:
            self.added = now()
        self.ident = slugify(self.name)
        super().save(force_insert, force_update, using, update_fields)

    def __str__(self):
        return str(self.name)

    @classmethod
    def with_records(cls, delta=False, asc=False):
        order = F("added").asc() if asc else F("added").desc()
        records = Record.objects.order_by(order)
        if delta:
            # add quantity delta between this and previous record
            # use lag or lead depending on order being used here
            adjacent = Window(
                expression=(Lag if asc else Lead)("quantity"),
                partition_by=F("item_id"),
                order_by=order,
            )
            # find difference, convert to item's unit and cast to correct decimal places
            expression = Cast(
                (F("quantity") - Coalesce(adjacent, 0)) /
                F("item__unit__convert"),
                DecimalField(max_digits=MAX_DIGITS,
                             decimal_places=DP_QUANTITY),
            )
            records = records.annotate(delta=expression)

        return (cls.objects.order_by("name").select_related(
            "unit").prefetch_related(Prefetch("records", records)))

    @classmethod
    def with_latest_record(cls):
        latest = Prefetch(
            "records",
            Record.objects.order_by("item_id", "-added").distinct("item_id"),
            "latest_records",
        )
        return (cls.objects.order_by("name").select_related(
            "unit").prefetch_related(latest))

    def get_absolute_url(self):
        return reverse("item_get", args=(self.ident, ))

    @property
    def latest_record(self):
        records = getattr(self, "latest_records")
        return records[0] if records else None

    def expected_end(self):
        average = getattr(self, "average")
        # calculate expected end only if latest record exists with non-zero quantity
        if self.latest_record is not None and self.latest_record.quantity and average:
            days = float(self.latest_record.quantity / average)
            return self.latest_record.added + timedelta(days=days)
        else:
            return None
Ejemplo n.º 22
0
class User(PermissionsMixin, TimeStampedModel, AbstractBaseUser):
    email = CITextField(
        verbose_name=_("Email address"),
        max_length=255,
        unique=True,
    )
    is_active = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    name = models.CharField(_("Name"), max_length=100, null=True)

    email_confirmation_token = models.CharField(max_length=64,
                                                default=secrets.token_hex,
                                                null=True)
    email_confirmation_datetime = models.DateTimeField(
        _("Email confirmation date"), editable=False, null=True)

    reset_password_token = models.CharField(max_length=64,
                                            null=True,
                                            editable=False)
    reset_password_token_expiration_date = models.DateTimeField(null=True,
                                                                editable=False)

    class Meta:
        db_table = "auth_user"

    def __str__(self):
        return self.email

    @property
    def full_name(self):
        return self.name

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        # Simplest possible answer: All admins are staff
        return self.is_admin

    def confirm_account(self):
        self.is_active = True
        self.email_confirmation_token = None
        self.email_confirmation_datetime = timezone.now()
        self.save()

    def gen_reset_password_token(self):
        self.reset_password_token = secrets.token_hex()
        self.reset_password_token_expiration_date = timezone.now() + timedelta(
            hours=24)
        self.save()

    def reset_password(self, password):
        self.set_password(password)
        self.reset_password_token = None
        self.reset_password_token_expiration_date = None
        self.save()
Ejemplo n.º 23
0
class Beer(models.Model):
    name = CITextField()
    style = models.ForeignKey(
        Style,
        models.DO_NOTHING,
        related_name='beers',
        blank=True,
        null=True,
    )
    manufacturer = models.ForeignKey(
        Manufacturer,
        models.CASCADE,
        related_name='beers',
    )
    in_production = models.BooleanField(default=True)
    abv = models.DecimalField(
        'Alcohol content (% by volume)',
        max_digits=4,
        decimal_places=2,
        blank=True,
        null=True,
    )
    ibu = models.PositiveSmallIntegerField(
        'Bitterness (International Bitterness Units)',
        blank=True,
        null=True,
    )
    color_srm = models.DecimalField(
        'Color (Standard Reference Method)',
        max_digits=4,
        decimal_places=1,
        blank=True,
        null=True,
    )
    untappd_url = models.URLField(blank=True, null=True, unique=True)
    beer_advocate_url = models.URLField(
        'BeerAdvocate URL (if known)',
        null=True,
        blank=True,
        unique=True,
    )
    rate_beer_url = models.URLField(blank=True, null=True, unique=True)
    logo_url = models.URLField(blank=True, null=True)
    color_html = models.CharField(
        'HTML Color (in hex)',
        max_length=9,  # #00112233 -> RGBA
        blank=True,
    )
    api_vendor_style = models.CharField(
        'API vendor-provided style (hidden from API)',
        max_length=100,
        blank=True,
    )
    manufacturer_url = models.URLField(blank=True, null=True, unique=True)
    automatic_updates_blocked = models.NullBooleanField(default=False)
    taphunter_url = models.URLField(blank=True, null=True, unique=True)
    stem_and_stein_pk = models.PositiveIntegerField(
        blank=True,
        null=True,
        unique=True,
    )
    taplist_io_pk = models.PositiveIntegerField(
        blank=True,
        null=True,
        unique=True,
    )
    time_first_seen = models.DateTimeField(blank=True, null=True, default=now)
    tweeted_about = models.BooleanField(default=False, db_index=True)
    beermenus_slug = models.CharField(
        max_length=250,
        blank=True,
        null=True,
        unique=True,
    )

    def save(self, *args, **kwargs):
        # force empty IDs to null to avoid running afoul of unique constraints
        if not self.untappd_url:
            self.untappd_url = None
        if not self.beer_advocate_url:
            self.beer_advocate_url = None
        if not self.rate_beer_url:
            self.rate_beer_url = None
        if not self.manufacturer_url:
            self.manufacturer_url = None
        if not self.taphunter_url:
            self.taphunter_url = None
        if not self.beermenus_slug:
            self.beermenus_slug = None
        return super().save(*args, **kwargs)

    def __str__(self):
        return self.name

    def render_srm(self):
        if self.color_html:
            return self.color_html
        return render_srm(self.color_srm)

    def merge_from(self, other):
        LOG.info('merging %s into %s', other, self)
        with transaction.atomic():
            Tap.objects.filter(beer=other).update(beer=self)
            BeerAlternateName.objects.filter(beer=other).update(beer=self)
            try:
                with transaction.atomic():
                    BeerPrice.objects.filter(beer=other).update(beer=self)
            except IntegrityError:
                LOG.warning('Duplicate prices detected for %s', self)
                prices_updated = BeerPrice.objects.filter(beer=other).exclude(
                    venue__in=models.Subquery(
                        BeerPrice.objects.filter(
                            beer=self).values('venue')), ).update(beer=self)
                prices_deleted = BeerPrice.objects.filter(beer=other).delete()
                LOG.info(
                    'Updated %s prices and deleted %s prices',
                    prices_updated,
                    prices_deleted,
                )
            excluded_fields = {
                'name'
                'in_production',
                'automatic_updates_blocked',
                'manufacturer',
                'id',
                'time_first_seen',
            }
            for field in self._meta.fields:
                field_name = field.name
                if field_name in excluded_fields:
                    continue
                other_value = getattr(other, field_name)
                if getattr(self, field_name) or not other_value:
                    # don't overwrite data that's already there
                    # or isn't set in the other one
                    continue
                setattr(self, field_name, other_value)
            self.automatic_updates_blocked = True
            if other.name != self.name:
                # this will only not happen if manufacturers aren't the same
                BeerAlternateName.objects.update_or_create(
                    name=other.name,
                    beer=self,
                )
            if other.time_first_seen:
                if not self.time_first_seen or \
                        self.time_first_seen > other.time_first_seen:
                    self.time_first_seen = other.time_first_seen
            other.delete()
            self.save()

    class Meta:
        unique_together = [
            ('name', 'manufacturer'),
        ]
Ejemplo n.º 24
0
class Manufacturer(models.Model):
    name = CITextField(unique=True)
    url = models.URLField(blank=True)
    location = models.CharField(blank=True, max_length=50)
    logo_url = models.URLField(blank=True)
    facebook_url = models.URLField(blank=True)
    twitter_handle = models.CharField(max_length=50, blank=True)
    instagram_handle = models.CharField(max_length=50, blank=True)
    untappd_url = models.URLField(blank=True, unique=True, null=True)
    automatic_updates_blocked = models.NullBooleanField(default=False)
    taphunter_url = models.URLField(blank=True, null=True, unique=True)
    taplist_io_pk = models.PositiveIntegerField(
        blank=True, null=True, unique=True,
    )
    time_first_seen = models.DateTimeField(blank=True, null=True, default=now)

    def merge_from(self, other):
        LOG.info('merging %s into %s', other, self)
        with transaction.atomic():
            other_beers = list(other.beers.all())
            my_beers = {i.name.casefold(): i for i in self.beers.all()}
            for beer in other_beers:
                beer.manufacturer = self
                if beer.name.casefold() in my_beers:
                    # we have a duplicate beer. Merge those two first.
                    # merge_from takes care of saving my_beer and deleting
                    # beer
                    # keep the one that was already present
                    my_beer = my_beers[beer.name.casefold()]
                    my_beer.merge_from(beer)
                else:
                    # good
                    beer.save()

            for alternate_name in other.alternate_names.all():
                alternate_name.beer = self
                alternate_name.save()
            excluded_fields = {
                'name', 'automatic_updates_blocked', 'id', 'time_first_seen',
            }
            for field in self._meta.fields:
                field_name = field.name
                if field_name in excluded_fields:
                    continue
                other_value = getattr(other, field_name)
                if getattr(self, field_name) or not other_value:
                    # don't overwrite data that's already there
                    # or isn't set in the other one
                    continue
                setattr(self, field_name, other_value)
            self.automatic_updates_blocked = True
            ManufacturerAlternateName.objects.update_or_create(
                name=other.name,
                manufacturer=self,
            )
            other.delete()
            if other.time_first_seen:
                if not self.time_first_seen or \
                        self.time_first_seen > other.time_first_seen:
                    self.time_first_seen = other.time_first_seen
            self.save()

    def __str__(self):
        return self.name
Ejemplo n.º 25
0
class OrgUnit(models.Model):
    VALIDATION_NEW = "NEW"
    VALIDATION_VALID = "VALID"
    VALIDATION_REJECTED = "REJECTED"

    VALIDATION_STATUS_CHOICES = (
        (VALIDATION_NEW, _("new")),
        (VALIDATION_VALID, _("valid")),
        (VALIDATION_REJECTED, _("rejected")),
    )

    name = models.CharField(max_length=255)
    uuid = models.TextField(null=True, blank=True, db_index=True)
    custom = models.BooleanField(default=False)
    validated = models.BooleanField(
        default=True, db_index=True)  # TO DO : remove in a later migration
    validation_status = models.CharField(max_length=25,
                                         choices=VALIDATION_STATUS_CHOICES,
                                         default=VALIDATION_NEW)
    version = models.ForeignKey("SourceVersion",
                                null=True,
                                blank=True,
                                on_delete=models.CASCADE)
    parent = models.ForeignKey("OrgUnit",
                               on_delete=models.CASCADE,
                               null=True,
                               blank=True)
    path = PathField(null=True, blank=True, unique=True)
    aliases = ArrayField(CITextField(max_length=255, blank=True),
                         size=100,
                         null=True,
                         blank=True)

    org_unit_type = models.ForeignKey(OrgUnitType,
                                      on_delete=models.CASCADE,
                                      null=True,
                                      blank=True)

    sub_source = models.TextField(
        null=True,
        blank=True)  # sometimes, in a given source, there are sub sources
    source_ref = models.TextField(null=True, blank=True, db_index=True)
    geom = MultiPolygonField(null=True, blank=True, srid=4326, geography=True)
    simplified_geom = MultiPolygonField(null=True,
                                        blank=True,
                                        srid=4326,
                                        geography=True)
    catchment = MultiPolygonField(null=True,
                                  blank=True,
                                  srid=4326,
                                  geography=True)
    geom_ref = models.IntegerField(null=True, blank=True)

    gps_source = models.TextField(null=True, blank=True)
    location = PointField(null=True,
                          blank=True,
                          geography=True,
                          dim=3,
                          srid=4326)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    creator = models.ForeignKey(User,
                                null=True,
                                blank=True,
                                on_delete=models.SET_NULL)

    objects = OrgUnitManager.from_queryset(OrgUnitQuerySet)()

    class Meta:
        indexes = [GistIndex(fields=["path"], buffering=True)]

    def save(self, *args, skip_calculate_path: bool = False, **kwargs):
        """Override default save() to make sure that the path property is calculated and saved,
        for this org unit and its children.

        :param skip_calculate_path: use with caution - can be useful in scripts where the extra transactions
                                    would be a burden, but the path needs to be set afterwards
        """

        if skip_calculate_path:
            super().save(*args, **kwargs)
        else:
            with transaction.atomic():
                super().save(*args, **kwargs)
                OrgUnit.objects.bulk_update(self.calculate_paths(), ["path"])

    def calculate_paths(self,
                        force_recalculate: bool = False
                        ) -> typing.List["OrgUnit"]:
        """Calculate the path for this org unit and all its children.

        This method will check if this org unit path should change. If it is the case (or if force_recalculate is
        True), it will update the path property for the org unit and its children, and return all the modified
        records.

        Please note that this method does not save the modified records. Instead, they are updated in bulk in the
        save() method.

        :param force_recalculate: calculate path for all descendants, even if this org unit path does not change
        """

        # For now, we will skip org units that have a parent without a path.
        # The idea is that a management command (set_org_unit_path) will handle the initial seeding of the
        # path field, starting at the top of the pyramid. Once this script has been run and the field is filled for
        # all org units, this should not happen anymore.
        # TODO: remove condition below
        if self.parent is not None and self.parent.path is None:
            return []

        # keep track of updated records
        updated_records = []

        base_path = [] if self.parent is None else list(self.parent.path)
        new_path = [*base_path, str(self.pk)]
        path_has_changed = new_path != self.path

        if path_has_changed:
            self.path = new_path
            updated_records += [self]

        if path_has_changed or force_recalculate:
            for child in self.orgunit_set.all():
                updated_records += child.calculate_paths(force_recalculate)

        return updated_records

    def __str__(self):
        return "%s %s %d" % (self.org_unit_type, self.name,
                             self.id if self.id else -1)

    def as_dict_for_mobile_lite(self):
        return {
            "n": self.name,
            "id": self.id,
            "p": self.parent_id,
            "out": self.org_unit_type_id,
            "c_a": self.created_at.timestamp() if self.created_at else None,
            "lat": self.location.y if self.location else None,
            "lon": self.location.x if self.location else None,
            "alt": self.location.z if self.location else None,
        }

    def as_dict_for_mobile(self):
        return {
            "name":
            self.name,
            "id":
            self.id,
            "parent_id":
            self.parent_id,
            "org_unit_type_id":
            self.org_unit_type_id,
            "org_unit_type_name":
            self.org_unit_type.name if self.org_unit_type else None,
            "validation_status":
            self.validation_status if self.org_unit_type else None,
            "created_at":
            self.created_at.timestamp() if self.created_at else None,
            "updated_at":
            self.updated_at.timestamp() if self.updated_at else None,
            "latitude":
            self.location.y if self.location else None,
            "longitude":
            self.location.x if self.location else None,
            "altitude":
            self.location.z if self.location else None,
        }

    def as_dict(self, with_groups=True):
        res = {
            "name":
            self.name,
            "short_name":
            self.name,
            "id":
            self.id,
            "source":
            self.version.data_source.name if self.version else None,
            "source_ref":
            self.source_ref,
            "parent_id":
            self.parent_id,
            "org_unit_type_id":
            self.org_unit_type_id,
            "org_unit_type_name":
            self.org_unit_type.name if self.org_unit_type else None,
            "created_at":
            self.created_at.timestamp() if self.created_at else None,
            "updated_at":
            self.updated_at.timestamp() if self.updated_at else None,
            "aliases":
            self.aliases,
            "validation_status":
            self.validation_status,
            "latitude":
            self.location.y if self.location else None,
            "longitude":
            self.location.x if self.location else None,
            "altitude":
            self.location.z if self.location else None,
            "has_geo_json":
            True if self.simplified_geom else False,
            "version":
            self.version.number if self.version else None,
        }

        if hasattr(self, "search_index"):
            res["search_index"] = self.search_index
        return res

    def as_dict_with_parents(self, light=False, light_parents=True):
        res = {
            "name":
            self.name,
            "short_name":
            self.name,
            "id":
            self.id,
            "sub_source":
            self.sub_source,
            "sub_source_id":
            self.sub_source,
            "source_ref":
            self.source_ref,
            "source_url":
            self.version.data_source.credentials.url
            if self.version and self.version.data_source
            and self.version.data_source.credentials else None,
            "parent_id":
            self.parent_id,
            "validation_status":
            self.validation_status,
            "parent_name":
            self.parent.name if self.parent else None,
            "parent":
            self.parent.as_dict_with_parents(light=light_parents,
                                             light_parents=light_parents)
            if self.parent else None,
            "org_unit_type_id":
            self.org_unit_type_id,
            "created_at":
            self.created_at.timestamp() if self.created_at else None,
            "updated_at":
            self.updated_at.timestamp() if self.updated_at else None,
            "aliases":
            self.aliases,
            "latitude":
            self.location.y if self.location else None,
            "longitude":
            self.location.x if self.location else None,
            "altitude":
            self.location.z if self.location else None,
            "has_geo_json":
            True if self.simplified_geom else False,
        }
        if not light:  # avoiding joins here
            res["groups"] = [
                group.as_dict(with_counts=False)
                for group in self.groups.all()
            ]
            res["org_unit_type_name"] = self.org_unit_type.name if self.org_unit_type else None
            res["org_unit_type"] = self.org_unit_type.as_dict(
            ) if self.org_unit_type else None
            res["source"] = self.version.data_source.name if self.version else None
            res["source_id"] = self.version.data_source.id if self.version else None
            res["version"] = self.version.number if self.version else None
        if hasattr(self, "search_index"):
            res["search_index"] = self.search_index
        return res

    def as_small_dict(self):
        res = {
            "name":
            self.name,
            "id":
            self.id,
            "parent_id":
            self.parent_id,
            "validation_status":
            self.validation_status,
            "parent_name":
            self.parent.name if self.parent else None,
            "source":
            self.version.data_source.name if self.version else None,
            "source_ref":
            self.source_ref,
            "parent":
            self.parent.as_small_dict() if self.parent else None,
            "org_unit_type_name":
            self.org_unit_type.name if self.org_unit_type else None,
        }
        if hasattr(self, "search_index"):
            res["search_index"] = self.search_index
        return res

    def as_dict_for_csv(self):
        return {
            "name": self.name,
            "id": self.id,
            "source_ref": self.source_ref,
            "parent_id": self.parent_id,
            "org_unit_type": self.org_unit_type.name,
        }

    def as_location(self):
        res = {
            "id":
            self.id,
            "name":
            self.name,
            "short_name":
            self.name,
            "latitude":
            self.location.y if self.location else None,
            "longitude":
            self.location.x if self.location else None,
            "altitude":
            self.location.z if self.location else None,
            "has_geo_json":
            True if self.simplified_geom else False,
            "org_unit_type":
            self.org_unit_type.name if self.org_unit_type else None,
            "org_unit_type_depth":
            self.org_unit_type.depth if self.org_unit_type else None,
            "source_id":
            self.version.data_source.id if self.version else None,
            "source_name":
            self.version.data_source.name if self.version else None,
        }
        if hasattr(self, "search_index"):
            res["search_index"] = self.search_index
        return res

    def source_path(self):
        """DHIS2-friendly path built using source refs"""

        path_components = []
        cur = self
        while cur:
            if cur.source_ref:
                path_components.insert(0, cur.source_ref)
            cur = cur.parent
        if len(path_components) > 0:
            return "/" + ("/".join(path_components))
        return None