Пример #1
0
class Account(Model):
    OPEN = 'OPEN'
    CLOSED = 'CLOSED'
    STATUS_CHOICES = (
        (OPEN, _('Open')),
        (CLOSED, _('Closed')),
    )
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created = models.DateTimeField(auto_now_add=True, db_index=True)
    modified = models.DateTimeField(auto_now=True)
    owner = models.OneToOneField(settings.AUTH_USER_MODEL,
                                 related_name='billing_account',
                                 on_delete=PROTECT)
    currency = CurrencyField(db_index=True)
    status = FSMField(max_length=20,
                      choices=STATUS_CHOICES,
                      default=OPEN,
                      db_index=True)

    objects = AccountQuerySet.as_manager()

    def balance(self, as_of: date = None):
        charges = Charge.objects.filter(account=self)
        transactions = Transaction.successful.filter(account=self)
        if as_of is not None:
            charges = charges.filter(created__lte=as_of)
            transactions = transactions.filter(created__lte=as_of)
        return total_amount(transactions) - total_amount(charges)

    @transition(field=status, source=OPEN, target=CLOSED)
    def close(self):
        pass

    @transition(field=status, source=CLOSED, target=OPEN)
    def reopen(self):
        pass

    def __str__(self):
        return str(self.owner)
Пример #2
0
class Research(models.Model):
    claim = models.ForeignKey(Claim, related_name="research_set", on_delete=models.CASCADE)

    truth = models.IntegerField(_("Truth"), null=True)
    content = models.TextField(blank=True)
    sources = models.ManyToManyField("Source", verbose_name=_("Sources"), related_name='research')
    researcher = models.ForeignKey("user.User", related_name="researched", on_delete=models.PROTECT)
    researched = models.DateTimeField(null=True)

    verification = models.ForeignKey("Verification", related_name="+", null=True, on_delete=models.SET_NULL)
    arbitration = models.ForeignKey("Arbitration", related_name="+", null=True, on_delete=models.SET_NULL)

    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    RESEARCHING = 'researching'
    RESEARCHED = 'researched'
    VERIFYING = 'verifying'
    VERIFIED = 'verified'
    CONTENTION = 'contention'  # a type of Research
    ARBITRATE = 'arbitrate'
    ARBITRATING = 'arbitrating'
    COMPLETED = 'completed'
    PHASES = (
        (RESEARCHING, pgettext_lazy('research.status', 'Researching')),
        (RESEARCHED, pgettext_lazy('research.status', 'Researched')),
        (VERIFYING, pgettext_lazy('research.status', 'Verifying')),
        (VERIFIED, pgettext_lazy('research.status', 'Verified')),
        (CONTENTION, pgettext_lazy('research.status', 'Contention')),
        (ARBITRATE, pgettext_lazy('research.status', 'Needs Arbitration')),
        (ARBITRATING, pgettext_lazy('research.status', 'Arbitrating')),
        (COMPLETED, pgettext_lazy('research.status', 'Completed')),
    )
    phase = FSMField(choices=PHASES, default=RESEARCHING)

    objects = ResearchQuerySet.as_manager()

    def is_completed(self):
        return self.phase == Research.COMPLETED
Пример #3
0
class OrderPayment(with_metaclass(WorkflowMixinMetaclass, models.Model)):
    """
    A model to hold received payments for a given order.
    """
    order = deferred.ForeignKey(BaseOrder, verbose_name=_("Order"))
    status = FSMField(default='new', protected=True, verbose_name=_("Status"))
    amount = MoneyField(
        _("Amount paid"),
        help_text=_("How much was paid with this particular transfer."))
    transaction_id = models.CharField(
        _("Transaction ID"),
        max_length=255,
        help_text=_("The transaction processor's reference"))
    created_at = models.DateTimeField(_("Received at"), auto_now_add=True)
    payment_method = models.CharField(
        _("Payment method"),
        max_length=255,
        help_text=_("The payment backend used to process the purchase"))

    class Meta:
        verbose_name = pgettext_lazy('order_models', "Order payment")
        verbose_name_plural = pgettext_lazy('order_models', "Order payments")
Пример #4
0
class Order(BaseModel):
    """
    This Model is aimed to manage customer Orders contain a state field that manager can change any time.
    """

    customer = models.ForeignKey(
        User,
        verbose_name="Customer",
        related_name='orders',
        on_delete=models.PROTECT,
    )

    state = FSMField(
        default='waiting',
        protected=True,
        verbose_name="Status",
    )

    total_price = models.PositiveIntegerField("Total Price", default=0)

    @transition(field=state, source="waiting", target="preparation")
    def prepare(self):
        # send email to customer
        pass

    @transition(field=state, source="preparation", target="ready")
    def ready(self):
        # send email to customer
        pass

    @transition(field=state, source="ready", target="delivered")
    def deliver(self):
        # send email to customer
        pass

    @transition(field=state, source="waiting", target="canceled")
    def cancel(self):
        # send email to customer
        pass
Пример #5
0
class Bloggs(models.Model):
    blogger = models.ForeignKey(blogger, related_name="bloggs")
    blog_type = models.ForeignKey(BlogType, related_name="bloggs")
    title = models.CharField(max_length=200)
    is_public = models.BooleanField(default=False)
    description = models.CharField(max_length=1000)
    state = FSMField(default='new')

    def __str__(self):
        return self.title

    @transition(field=state, source=["new", "draft"], target="draft")
    def draft(self):
        pass

    @transition(field=state, source=["new", "draft"], target="publish")
    def draft(self):
        pass

    @transition(field=state, source=["publish"], target="cancel")
    def draft(self):
        pass
Пример #6
0
class Order(models.Model):
    customer = models.CharField(max_length=255)
    address = models.CharField(max_length=1000)
    item = models.CharField(max_length=255)
    price = models.DecimalField(max_digits=9, decimal_places=2)

    STATE_CHOICES = (("ordered", "Ordered", "Order"),
                     ("shipped", "Shipped", "Order"), ("returned", "Returned",
                                                       "CancelledOrder"),
                     ("cancelled", "Cancelled", "CancelledOrder"))
    state = FSMField(default="ordered", state_choices=STATE_CHOICES)

    def refund(self):
        # return the money
        pass

    @transition(field=state, source="ordered", target="shipped")
    def ship(self):
        # send notification email
        pass

    def not_too_expensive_for_return(self):
        return self.price <= 200

    @transition(field=state,
                source="shipped",
                target="returned",
                conditions=[not_too_expensive_for_return],
                permission=lambda instance, user: user not in BLACKLIST)
    def receive_return(self):
        self.refund()

    @transition(field=state, source="ordered", target="cancelled")
    def cancel(self):
        self.refund()

    @property
    def revenue(self):
        return self.price
Пример #7
0
class Article(models.Model):
    STATES = (
        ('draft', 'Draft'),
        ('submitted', 'Article submitted'),
        ('published', 'Article published'),
        ('deleted', 'Article deleted'),
    )

    state = FSMField(choices=STATES, default='draft', protected=True)

    @fsm_log_by
    @fsm_log_description
    @transition(field=state, source='draft', target='submitted')
    def submit(self, description=None, by=None):
        pass

    @fsm_log_by
    @transition(field=state, source='submitted', target='draft')
    def request_changes(self, by=None):
        pass

    @fsm_log_by
    @transition(field=state, source='submitted', target='published')
    def publish(self, by=None):
        pass

    @fsm_log_by
    @transition(field=state, source='*', target='deleted')
    def delete(self, using=None):
        pass

    @fsm_log_by
    @fsm_log_description(allow_inline=True)
    @transition(field=state, source='draft', target='submitted')
    def submit_inline_description_change(self,
                                         change_to,
                                         description=None,
                                         by=None):
        description.set(change_to)
Пример #8
0
class Insect(models.Model):
    class STATE:
        CATERPILLAR = 'CTR'
        BUTTERFLY = 'BTF'

    STATE_CHOICES = ((STATE.CATERPILLAR, 'Caterpillar', 'Caterpillar'),
                     (STATE.BUTTERFLY, 'Butterfly', 'Butterfly'))

    state = FSMField(default=STATE.CATERPILLAR, state_choices=STATE_CHOICES)

    @transition(field=state, source=STATE.CATERPILLAR, target=STATE.BUTTERFLY)
    def cocoon(self):
        pass

    def fly(self):
        raise NotImplementedError

    def crawl(self):
        raise NotImplementedError

    class Meta:
        app_label = 'testapp'
Пример #9
0
class BlogPostWithConditions(models.Model):
    state = FSMField(default='new')

    def model_condition(self):
        return True

    def unmet_condition(self):
        return False

    @transition(field=state,
                source='new',
                target='published',
                conditions=[condition_func, model_condition])
    def publish(self):
        pass

    @transition(field=state,
                source='published',
                target='destroyed',
                conditions=[condition_func, unmet_condition])
    def destroy(self):
        pass
Пример #10
0
class Application(models.Model):
    class Meta:
        verbose_name = "Application Form"
        verbose_name_plural = "Application Forms"
        ordering = ('created', )

    name = models.CharField(max_length=512)
    description = models.TextField()
    position = models.ForeignKey('user.Position',
                                 related_name='applications',
                                 on_delete=models.CASCADE)
    created = models.DateTimeField(auto_now_add=True)
    published = models.DateTimeField(null=True)

    DRAFT = "draft"
    READY = "ready"
    LIVE = "live"

    STATE_CHOICES = (
        (DRAFT, "Draft"),
        (READY, "Ready"),
        (LIVE, "Live"),
    )

    state = FSMField(default=DRAFT, choices=STATE_CHOICES)

    objects = ApplicationQuerySet.as_manager()

    @transition(field=state, source=[DRAFT], target=READY)
    def approve(self):
        pass

    @transition(field=state, source=[READY], target=LIVE)
    def publish(self):
        self.published = timezone.now()

    def __str__(self):
        return self.name
Пример #11
0
class MultiResultTest(models.Model):
    state = FSMField(default='new')

    @transition(
        field=state,
        source='new',
        target=RETURN_VALUE('for_moderators', 'published'))
    def publish(self, is_public=False):
        return 'published' if is_public else 'for_moderators'

    @transition(
        field=state,
        source='for_moderators',
        target=GET_STATE(
            lambda self, allowed: 'published' if allowed else 'rejected',
            states=['published', 'rejected']
        )
    )
    def moderate(self, allowed):
        pass

    class Meta:
        app_label = 'testapp'
Пример #12
0
class MemberApplication(models.Model):
    applicant = models.OneToOneField(
        settings.AUTH_USER_MODEL, related_name='applicant')
    mobile = models.CharField('手机号码', max_length=100, blank=True)
    job_title = models.CharField('职位', max_length=100, blank=True)
    company_title = models.CharField('公司名称', max_length=255, blank=True)
    company = models.ForeignKey(Company, blank=True, null=True)
    info = models.TextField('其它信息', blank=True)
    reason = models.TextField('拒绝理由', blank=True)
    auditor = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name='auditor', null=True, blank=True)
    state = FSMField(default='new')
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return '{0} - {1}'.format(self.company_title, self.applicant)

    def has_auditor(self):
        return self.auditor is not None

    def has_company_assoc(self):
        return self.company is not None and self.auditor is not None

    def can_approve(self):
        return self.has_auditor() and self.has_company_assoc()

    @transition(field=state, source='new', target='approved',
                conditions=[can_approve])
    def approved(self):
        notify.send(self.auditor, recipient=self.applicant, verb='审核通过')

    @transition(field=state, source='new', target='denied',
                conditions=[has_auditor])
    def denied(self):
        notify.send(self.auditor, recipient=self.applicant,
                    verb='拒绝', description=self.reason)
Пример #13
0
class AuthResult(core_models.UuidMixin, core_models.ErrorMessageMixin, TimeStampedModel):
    class States:
        SCHEDULED = 'Scheduled'
        PROCESSING = 'Processing'
        OK = 'OK'
        CANCELED = 'Canceled'
        ERRED = 'Erred'

        CHOICES = ((SCHEDULED, SCHEDULED), (PROCESSING, PROCESSING), (OK, OK), (CANCELED, CANCELED), (ERRED, ERRED))

    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='auth_valimo_results', null=True)
    phone = models.CharField(max_length=30)
    message = models.CharField(max_length=4, default=_default_message, help_text='This message will be shown to user.')
    state = FSMField(choices=States.CHOICES, default=States.SCHEDULED)
    details = models.CharField(max_length=255, blank=True, help_text='Cancellation details.')
    backend_transaction_id = models.CharField(max_length=100, blank=True)

    # for consistency with other models with state
    @property
    def human_readable_state(self):
        return self.state

    @transition(field=state, source=States.SCHEDULED, target=States.PROCESSING)
    def begin_processing(self):
        pass

    @transition(field=state, source=States.PROCESSING, target=States.OK)
    def set_ok(self):
        pass

    @transition(field=state, source=States.PROCESSING, target=States.CANCELED)
    def set_canceled(self):
        pass

    @transition(field=state, source='*', target=States.ERRED)
    def set_erred(self):
        pass
Пример #14
0
class Take(models.Model):
    session = models.ForeignKey(Session,
                                related_name="takes",
                                on_delete=CASCADE)
    name = models.CharField(max_length=255)
    number = models.IntegerField()
    started_at = models.DateTimeField(auto_now_add=True)
    location = models.FloatField()
    length = models.FloatField(null=True)
    take_mix_source = models.CharField(max_length=1024, null=True)
    take_mix_processed = models.CharField(max_length=1024, null=True)

    state = FSMField(default="started")

    @transition(field=state, source="started", target="queued")
    def stop(self):
        pass

    @transition(field=state, source="started", target="canceled")
    def cancel(self):
        pass

    @transition(field=state, source="recorded", target="queued")
    def queue(self):
        pass

    @transition(field=state, source="queued", target="recorded")
    def unqueue(self):
        pass

    @transition(field=state, source="queued", target="processing")
    def processing_started(self):
        pass

    @transition(field=state, source="processing", target="uploaded")
    def upload_finished(self):
        pass
Пример #15
0
class StatisticsEntry(ExportModelOperationsMixin("attendance.StatisticsEntry"),
                      models.Model):
    statistics = models.ForeignKey("Statistics",
                                   models.CASCADE,
                                   related_name="entries")
    incoming = models.ForeignKey("Entry", models.CASCADE, related_name="+")
    outgoing = models.ForeignKey("Entry",
                                 models.CASCADE,
                                 null=True,
                                 blank=True,
                                 related_name="+")
    state = FSMField(default="created")

    class Meta:
        unique_together = (("statistics", "incoming"), )
        ordering = ("incoming__created", )
        get_latest_by = "incoming__created"

    @transition(field=state, source="created", target="completed")
    def complete(self, entry=None):
        self.outgoing = entry

    def __str__(s):
        return f"{s.statistics}: {s.incoming}/{s.outgoing}"
Пример #16
0
class Season(models.Model):
    """Season model"""
    class Meta:
        verbose_name = "Выезжавший на сезон"
        verbose_name_plural = "Выезжавшие на сезон"

    boec = models.ForeignKey(Boec,
                             on_delete=models.CASCADE,
                             verbose_name="ФИО",
                             related_name="seasons")

    class SeasonState(TextChoices):
        INITIAL = "initial", _("Не подтвержен")
        ACCEPTED = "accepted", _("Зачтен")
        NOT_ACCEPTED = "rejected", _("Не зачтен")

    state = FSMField(
        default=SeasonState.INITIAL,
        choices=SeasonState.choices,
        verbose_name="Зачтенность",
    )

    def __str__(self):
        return self.boec.full_name
Пример #17
0
class Product(models.Model):
    DRAFT = 'draft'
    PUBLISHED = 'published'
    STATES = (
        (DRAFT, 'Draft'),
        (PUBLISHED, 'Published'),
    )
    name = models.CharField(max_length=255)
    price = models.IntegerField()
    currency = models.CharField(choices=CURRENCIES, default=EUR, max_length=3)
    status = FSMField(default=DRAFT, protected=True, choices=STATES)

    objects = ProductManager()

    @transition(field=status, source=DRAFT, target=PUBLISHED)
    def publish(self):
        pass

    @transition(field=status, source=PUBLISHED, target=DRAFT)
    def unpublish(self):
        pass

    def __str__(self):
        return self.name
Пример #18
0
class Text(models.Model):
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    name = models.CharField(max_length=255)
    text = models.TextField()
    state = FSMField(default='new', protected=True)

    @transition(field=state, source='new', target='done')
    def processed(self):
        """
        Side effects
        """

    @transition(field=state, source='done', target='new')
    def renew(self):
        """
        Side effects
        """

    def save_model(self, request, obj, form, change):
        obj.created_by = request.user
        obj.save()

    def __str__(self):
        return '{name} ({state})'.format(name=self.name, state=self.state)
Пример #19
0
class Organization(core_models.AbstractBaseModel):

    STATE_CHOICES = (
        ("CURRENT", "Current"),
        ("NAN", "No Approval Necessary"),
        ("LAPSED", "Lapsed"),
    )

    SUBSCRIPTION_LEVEL_CHOICES = (
        ("FREE", "Free"),
        ("TRIAL", "Trial"),
        ("PRO", "Professional"),
    )

    name = models.CharField("Name", max_length=96, null=False)
    notes = models.TextField("Notes", null=True, blank=True)
    state = FSMField(
        default="CURRENT",
        verbose_name="Status",
        choices=STATE_CHOICES,
        # protected=True,
    )
    subscription_level = models.CharField("Subscription Level",
                                          max_length=24,
                                          default="FREE")
    tenant_id = "id"

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

    # States:
    # –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
    # CURRENT - tenant is active and current with account
    # NO APPROVAL NEEDED - tenant exists outside of approval structure (admin/pro bono)
    # LAPSED - tenant is inactive or account is past due
    # –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
    # Users of tenants that are CURRENT can log in normally
    # Users of tenants that are NO APPROVAL NEEDED can log in normally
    # Users of tenants that are LAPSED can log in on a read-only basis

    @fsm_log_by
    @transition(
        field=state,
        source="*",
        target="CURRENT",
        permission="app.change_tenant_status",
    )
    def approve(self, by=None):
        return

    @fsm_log_by
    @transition(
        field=state,
        source="*",
        target="NAN",
        permission="app.change_tenant_status",
    )
    def no_approval_needed(self, by=None):
        return

    @fsm_log_by
    @transition(
        field=state,
        source="*",
        target="LAPSED",
        permission="app.change_tenant_status",
    )
    def lapsed(self, by=None):
        return

    class Meta:
        ordering = ["name"]
        permissions = [
            ("change_tenant_status", "Can change status of tenant"),
        ]
Пример #20
0
class Candidate(models.Model):
    SHORTLIST = 'shortlist'
    TECHNICAL = 'technical'
    PRACTICAL = 'practical'
    HR = 'hr'
    REJECTED = 'rejected'
    SELECTED = 'selected'
    STATUS_TAG = [
        (SHORTLIST, 'Short Listed'),
        (TECHNICAL, 'For Technical Round'),
        (PRACTICAL, 'For Practical Round'),
        (HR, 'For HR Round'),
        (REJECTED, 'Rejected'),
        (SELECTED, 'Selected'),
    ]
    EXPERIENCE_CHOICE = [
        ('2', '0 - 1'),
        ('1 - 2', '1 - 2'),
        ('2 - 3', '2 - 3'),
        ('3 - Above', '3 - Above'),
    ]
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    birth_date = models.DateField()
    mobile = PhoneNumberField()
    email = models.EmailField(unique=True)
    token = models.CharField(max_length=30, null=True, blank=True)
    resume = models.FileField(upload_to='resumes/', null=True, blank=True)
    applied_for = models.ForeignKey(Vacancies, on_delete=models.CASCADE)
    status = FSMField(default=SHORTLIST, choices=STATUS_TAG)
    experience = models.CharField(default='0 - 1',
                                  choices=EXPERIENCE_CHOICE,
                                  max_length=20)
    is_verified = models.BooleanField(default=False)

    def __str__(self):
        return self.first_name

    def get_absolute_url(self):
        return reverse('scheduler:candidate_detail', args=[self.pk])

    @transition(field='status', source=SHORTLIST, target=TECHNICAL)
    def technical_round(self):
        pass

    @transition(field='status', source=TECHNICAL, target=PRACTICAL)
    def practical_round(self):
        pass

    @transition(field='status', source=PRACTICAL, target=HR)
    def hr_round(self):
        pass

    @transition(field='status', source=HR, target=SELECTED)
    def select(self):
        pass

    @transition(field='status',
                source=[SHORTLIST, TECHNICAL, PRACTICAL, HR],
                target=REJECTED)
    def reject(self):
        pass
Пример #21
0
class Transaction(models.Model):
    amount = models.DecimalField(
        decimal_places=2,
        max_digits=12,
        validators=[MinValueValidator(Decimal('0.00'))])
    currency = models.CharField(choices=currencies,
                                max_length=4,
                                help_text='The currency used for billing.')

    class States:
        Initial = 'initial'
        Pending = 'pending'
        Settled = 'settled'
        Failed = 'failed'
        Canceled = 'canceled'
        Refunded = 'refunded'

        @classmethod
        def as_list(cls):
            return [
                getattr(cls, state) for state in vars(cls).keys()
                if state[0].isupper()
            ]

        @classmethod
        def as_choices(cls):
            return ((state, _(state.capitalize())) for state in cls.as_list())

    external_reference = models.CharField(max_length=256,
                                          null=True,
                                          blank=True)
    data = JSONField(default={}, null=True, blank=True)
    state = FSMField(max_length=8,
                     choices=States.as_choices(),
                     default=States.Initial)

    proforma = models.ForeignKey("Proforma", null=True, blank=True)
    invoice = models.ForeignKey("Invoice", null=True, blank=True)
    payment_method = models.ForeignKey('PaymentMethod')
    uuid = models.UUIDField(default=uuid.uuid4)
    valid_until = models.DateTimeField(null=True, blank=True)
    last_access = models.DateTimeField(null=True, blank=True)

    created_at = models.DateTimeField(default=timezone.now)
    updated_at = AutoDateTimeField(default=timezone.now)

    fail_code = models.CharField(choices=[(code, code)
                                          for code in FAIL_CODES.keys()],
                                 max_length=32,
                                 null=True,
                                 blank=True)
    refund_code = models.CharField(choices=[(code, code)
                                            for code in REFUND_CODES.keys()],
                                   max_length=32,
                                   null=True,
                                   blank=True)
    cancel_code = models.CharField(choices=[(code, code)
                                            for code in CANCEL_CODES.keys()],
                                   max_length=32,
                                   null=True,
                                   blank=True)

    @property
    def final_fields(self):
        fields = [
            'proforma', 'uuid', 'payment_method', 'amount', 'currency',
            'created_at'
        ]

        if self.invoice:
            fields.append('invoice')

        return fields

    def __init__(self, *args, **kwargs):
        self.form_class = kwargs.pop('form_class', None)

        super(Transaction, self).__init__(*args, **kwargs)

    @transition(field=state, source=States.Initial, target=States.Pending)
    def process(self):
        pass

    @transition(field=state,
                source=[States.Initial, States.Pending],
                target=States.Settled)
    def settle(self):
        pass

    @transition(field=state,
                source=[States.Initial, States.Pending],
                target=States.Canceled)
    def cancel(self,
               cancel_code='default',
               cancel_reason='Unknown cancel reason'):
        self.cancel_code = cancel_code
        logger.error(str(cancel_reason))

    @transition(field=state,
                source=[States.Initial, States.Pending],
                target=States.Failed)
    def fail(self, fail_code='default', fail_reason='Unknown fail reason'):
        self.fail_code = fail_code
        logger.error(str(fail_reason))

    @transition(field=state, source=States.Settled, target=States.Refunded)
    def refund(self,
               refund_code='default',
               refund_reason='Unknown refund reason'):
        self.refund_code = refund_code
        logger.error(str(refund_reason))

    def clean(self):
        document = self.document
        if not document:
            raise ValidationError(
                'The transaction must have at least one document '
                '(invoice or proforma).')

        if document.state == document.STATES.DRAFT:
            raise ValidationError(
                'The transaction must have a non-draft document '
                '(invoice or proforma).')

        if document.provider != self.provider:
            raise ValidationError(
                'Provider doesn\'t match with the one in documents.')

        if document.customer != self.customer:
            raise ValidationError(
                'Customer doesn\'t match with the one in documents.')

        if self.invoice and self.proforma:
            if self.invoice.proforma != self.proforma:
                raise ValidationError('Invoice and proforma are not related.')
        else:
            if self.invoice:
                self.proforma = self.invoice.proforma
            else:
                self.invoice = self.proforma.invoice

        # New transaction
        if not self.pk:
            if document.state != document.STATES.ISSUED:
                raise ValidationError(
                    'Transactions can only be created for issued documents.')

            if self.currency:
                if self.currency != self.document.transaction_currency:
                    raise ValidationError(
                        "Transaction currency is different from it's document's"
                        " transaction_currency.")
            else:
                self.currency = self.document.transaction_currency

            if (self.payment_method.allowed_currencies and self.currency
                    not in self.payment_method.allowed_currencies):
                raise ValidationError(
                    'Currency {} is not allowed by the payment method. '
                    'Allowed currencies are {}.'.format(
                        self.currency, self.payment_method.allowed_currencies))

            if self.amount:
                if self.amount != self.document.transaction_total:
                    raise ValidationError(
                        "Transaction amount is different from it's document's "
                        "transaction_total.")
            else:
                self.amount = self.document.transaction_total

            # We also check for settled because document pay transition might fail
            if self.document.transactions.filter(state__in=[
                    Transaction.States.Initial, Transaction.States.Pending,
                    Transaction.States.Settled
            ]).exists():
                raise ValidationError(
                    'There already are active transactions for the same '
                    'billing documents.')

    def full_clean(self, *args, **kwargs):
        # 'amount' and 'currency' are handled in our clean method
        kwargs['exclude'] = kwargs.get('exclude', []) + ['currency', 'amount']
        super(Transaction, self).full_clean(*args, **kwargs)

        # this assumes that nobody calls clean and then modifies this object
        # without calling clean again
        self.cleaned = True

    @property
    def can_be_consumed(self):
        if self.valid_until and self.valid_until < timezone.now():
            return False

        if self.state != Transaction.States.Initial:
            return False

        return True

    @property
    def customer(self):
        return self.payment_method.customer

    @property
    def document(self):
        return self.invoice or self.proforma

    @property
    def provider(self):
        return self.document.provider

    @property
    def payment_processor(self):
        return self.payment_method.payment_processor

    def update_document_state(self):
        if (self.state == Transaction.States.Settled
                and self.document.state != self.document.STATES.PAID):
            self.document.pay()
            self.document.save()

    def __unicode__(self):
        return unicode(self.uuid)
Пример #22
0
class Playlist(TimeStampedModel):
    table = models.ForeignKey('users.Table',
                              on_delete=models.SET_DEFAULT,
                              null=True,
                              related_name='playlist',
                              default=None,
                              verbose_name=_('Requester'))
    music = models.ForeignKey(Music,
                              on_delete=models.CASCADE,
                              related_name='playlist',
                              verbose_name=_('Music'))
    is_active = models.BooleanField(default=True, verbose_name=_('Active?'))
    state = FSMField(choices=MUSIC_PLAYLIST_STATE_CHOICES,
                     default=MUSIC_PLAYLIST_STATE.default)

    def __str__(self):
        return "<%s(%d):%s>" % (_('Playlist'), self.pk, self.music.title)

    class Meta:
        ordering = ('created', )
        verbose_name = _('Playlist')
        verbose_name_plural = _('Playlist')

    def get_customer_name(self):
        if self.table is None:
            return None
        return self.table.name

    @property
    def title(self):
        return self.get_title()

    def get_title(self):
        return self.music.title

    @property
    def url(self):
        return self.get_url()

    def get_url(self):
        return self.music.music_url

    @property
    def pic_url(self):
        return self.get_pic_url()

    def get_pic_url(self):
        return self.music.picture_url

    @property
    def artist(self):
        return self.music.author

    @property
    def identifier(self):
        return self.music.external_id

    @classmethod
    def pendings(cls):
        return cls.objects.filter(state=MUSIC_PLAYLIST_STATE.default,
                                  is_active=True,
                                  music__provider=MUSIC_PROVIDER.spotify,
                                  music__state=MUSIC_STATE.ready)
Пример #23
0
class Music(TimeStampedModel):
    title = models.CharField(max_length=64, verbose_name=_('Title'))
    author = models.CharField(max_length=32, verbose_name=_('Author'))
    url = models.URLField(verbose_name=_('Music url'))
    external_id = models.CharField(max_length=32,
                                   unique=True,
                                   verbose_name=_('External ID'))
    pic_url = models.URLField(verbose_name=_('Picture URL'))
    provider = FSMField(choices=MUSIC_PROVIDER_CHOICES,
                        default=MUSIC_PROVIDER.ting,
                        verbose_name=_('Music Provider'))
    state = FSMField(choices=MUSIC_STATE_CHOICES,
                     default=MUSIC_STATE.default,
                     verbose_name=_('State'))
    details = JSONField(default={}, verbose_name=_('Details'))

    class Meta:
        ordering = ('title', )
        verbose_name = _('Music')
        verbose_name_plural = _('Musics')

    def __str__(self):
        return "<%s(%d):%s>" % (_('Music'), self.pk, self.title)

    @classmethod
    def external_search(cls, keyword):
        # response = Ting.search_music(keyword)
        response = Spotify.search(keyword)
        return response

    @classmethod
    def find_music(cls, external_id, provider=MUSIC_PROVIDER.spotify):
        exist, instance = cls.exists(external_id, provider=provider)
        if exist:
            return True, instance
        else:
            instance = cls(external_id=external_id, provider=provider)
            is_valid = instance.clean()
            if is_valid:
                instance.save()
            return is_valid, instance

    def clean(self):
        super(Music, self).clean()
        external_id = self.external_id

        if self.provider == MUSIC_PROVIDER.ting:
            response_data = Ting.retrieve_music(external_id)

            if not response_data.get('songinfo'):
                return False
            else:
                self.title = response_data['songinfo']['title']
                self.author = response_data['songinfo']['author']
                self.url = response_data['bitrate']['show_link']
                self.pic_url = response_data['songinfo']['pic_big']
                return True
        else:
            response_data = Spotify.retrieve_music(external_id)
            self.title = response_data['name']
            self.author = response_data['author']
            self.url = response_data['url']
            self.pic_url = response_data['pic_url']
            self.state = MUSIC_STATE.ready
            return True

    @classmethod
    def exists(cls, external_id, provider=MUSIC_PROVIDER.spotify):
        qs = cls.objects.filter(external_id=external_id, provider=provider)
        if qs.exists():
            return True, qs.first()
        else:
            return False, None

    @property
    def music_file_extension(self):
        return self.url.split('?')[0].split('/')[-1].split('.')[-1]

    @property
    def picture_file_extension(self):
        return self.pic_url.split('?')[0].split('/')[-1].split('.')[-1]

    @property
    def download_file_path(self):
        return "%s/%s.%s" % (settings.MUSIC_DOWNLOAD_PATH, self.external_id,
                             self.music_file_extension)

    @property
    def picture_download_file_path(self):
        return "%s/%s.%s" % (settings.MUSIC_DOWNLOAD_PATH, self.external_id,
                             self.picture_file_extension)

    @property
    def upload_key(self):
        return "media/music/%s/%s.%s" % (self.external_id, self.external_id,
                                         self.music_file_extension)

    @property
    def picture_upload_key(self):
        return "media/music/%s/%s.%s" % (self.external_id, self.external_id,
                                         self.picture_file_extension)

    @property
    def music_url(self):
        return "http://%s/%s" % (settings.AWS_S3_CUSTOM_DOMAIN,
                                 self.upload_key)

    @property
    def picture_url(self):
        return "http://%s/%s" % (settings.AWS_S3_CUSTOM_DOMAIN,
                                 self.picture_upload_key)

    @classmethod
    def get_invalid_musics(cls):
        return cls.objects.filter(
            modified__date__lte=datetime.datetime.now().date())

    @transition(field='state',
                source=MUSIC_STATE.default,
                target=MUSIC_STATE.downloading)
    def download(self):
        request.urlretrieve(self.url, self.download_file_path)
        request.urlretrieve(self.pic_url, self.picture_download_file_path)

    @transition(field='state',
                source=MUSIC_STATE.downloading,
                target=MUSIC_STATE.uploading)
    def upload(self):
        s3 = boto3.client('s3',
                          aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
                          aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY)
        bucket_name = settings.AWS_STORAGE_BUCKET_NAME
        s3.upload_file(self.download_file_path, bucket_name, self.upload_key)
        s3.upload_file(self.picture_download_file_path, bucket_name,
                       self.picture_upload_key)

    @transition(field='state',
                source=MUSIC_STATE.uploading,
                target=MUSIC_STATE.ready)
    def approve(self):
        if os.path.exists(self.download_file_path):
            os.remove(self.download_file_path)
        if os.path.exists(self.picture_download_file_path):
            os.remove(self.picture_download_file_path)

    def process(self):
        self.download()
        self.upload()
        self.approve()
        self.save()

    @classmethod
    def spotify_musics(cls):
        return cls.objects.filter(provider=MUSIC_PROVIDER.spotify)
Пример #24
0
class PurchaseIndentRequest(BaseModel):
    """This stores all the information regarding a purchase indent of an employee."""

    indenter = models.ForeignKey(Employee, on_delete=models.CASCADE)
    project_name = models.CharField(max_length=200)
    budget_head = models.CharField(max_length=50)
    category = models.CharField(max_length=50)
    make_or_model_reason = models.TextField(max_length=500,
                                            null=True,
                                            blank=True)
    proprietary_owner = models.CharField(max_length=100, null=True, blank=True)
    proprietary_distributor = models.CharField(max_length=100,
                                               null=True,
                                               blank=True)

    state = FSMField(blank=True,
                     protected=not settings.DEBUG,
                     default=STATE.SUBMITTED)

    budgetary_approval = models.ImageField(upload_to='budgetary_approval',
                                           blank=True)
    directors_approval = models.ImageField(upload_to='directors_approval',
                                           blank=True)
    project_approval = models.ImageField(upload_to='project_approval',
                                         blank=True)

    budget_sanctioned = models.DecimalField(max_digits=10,
                                            decimal_places=2,
                                            default=0)
    amount_already_spent = models.DecimalField(max_digits=10,
                                               decimal_places=2,
                                               default=0)
    budget_available = models.DecimalField(max_digits=10,
                                           decimal_places=2,
                                           default=0)
    expenditure_debitable_to = models.CharField(max_length=100,
                                                null=False,
                                                blank=False)

    @transition(field=state,
                source=STATE.SUBMITTED,
                target=STATE.APPROVED_BY_HOD)
    def hod_approve(self):
        """HOD approves the indent form."""
        print "HOD approved this form. Current state:", self.state

    @transition(field=state,
                source=STATE.APPROVED_BY_HOD,
                target=STATE.APPROVED_BY_JAO)
    def jao_approve(self):
        """JAO approves the indent form."""
        print "JAO approved this form. Current state:", self.state

    @transition(field=state,
                source=STATE.APPROVED_BY_JAO,
                target=STATE.APPROVED_BY_DR)
    def dr_approve(self):
        """DR approves the indent form."""
        print "DR approved this form. Current state:", self.state

    @transition(
        field=state,
        source=[STATE.SUBMITTED, STATE.APPROVED_BY_HOD, STATE.APPROVED_BY_JAO],
        target=STATE.REJECT)
    def reject(self):
        """Reject the indent form."""
        print "This form has been rejected. Current state:", self.state

    def __str__(self):
        """Return string representing the form as Name of Indentor[space]Created time."""
        return str(self.indenter.user.first_name) + ' ' \
            + str(self.indenter.user.last_name) + ' ' \
            + str(self.created_at)

    def __unicode__(self):
        """Return string representing the form as Name of Indentor[space]Created time."""
        return str(self.indenter.user.first_name) + ' ' \
            + str(self.indenter.user.last_name) + ' ' \
            + str(self.created_at)
Пример #25
0
class Payment(models.Model):
    objects = Manager.from_queryset(PaymentQuerySet)()

    amount = models.DecimalField(
        decimal_places=2,
        max_digits=8,
        validators=[MinValueValidator(Decimal('0.00'))])
    due_date = models.DateField(null=True, blank=True, default=None)

    class Status(object):
        Unpaid = 'unpaid'
        Pending = 'pending'
        Paid = 'paid'
        Canceled = 'canceled'

        FinalStatuses = [Paid, Canceled]

    STATUS_CHOICES = ((Status.Unpaid, _('Unpaid')),
                      (Status.Pending, _('Pending')), (Status.Paid, _('Paid')),
                      (Status.Canceled, _('Canceled')))
    status = FSMField(max_length=8,
                      choices=STATUS_CHOICES,
                      default=Status.Unpaid)

    customer = models.ForeignKey(Customer)
    provider = models.ForeignKey(Provider)
    proforma = models.OneToOneField("Proforma",
                                    null=True,
                                    blank=True,
                                    related_name='proforma_payment')
    invoice = models.OneToOneField("Invoice",
                                   null=True,
                                   blank=True,
                                   related_name='invoice_payment')
    visible = models.BooleanField(default=True)
    currency = models.CharField(choices=currencies,
                                max_length=4,
                                default='USD',
                                help_text='The currency used for billing.')
    currency_rate_date = models.DateField(blank=True, null=True)

    @transition(field='status',
                source=[Status.Unpaid, Status.Pending],
                target=Status.Canceled)
    def cancel(self):
        pass

    @transition(field='status', source=Status.Unpaid, target=Status.Pending)
    def process(self):
        pass

    @transition(field='status',
                source=(Status.Unpaid, Status.Pending),
                target=Status.Paid)
    def succeed(self):
        pass

    @transition(field='status', source=Status.Pending, target=Status.Unpaid)
    def fail(self):
        pass

    def clean(self):
        document = self.invoice or self.proforma
        if document:
            if document.provider != self.provider:
                raise ValidationError(
                    'Provider doesn\'t match with the one in documents.')

            if document.customer != self.customer:
                raise ValidationError(
                    'Customer doesn\'t match with the one in documents.')

        if self.invoice and self.proforma:
            if self.invoice.proforma != self.proforma:
                raise ValidationError('Invoice and proforma are not related.')

    def _log_unsuccessful_transition(self, transition_name):
        logger.warning(
            '[Models][Payment]: %s', {
                'detail': 'Couldn\'t %s payment' % transition_name,
                'payment_id': self.id,
                'customer_id': self.customer.id
            })

    @property
    def is_overdue(self):
        if self.status == Payment.Status.Unpaid and self.days_left <= 0:
            return True

        return False

    @property
    def days_left(self):
        return (self.due_date - date.today()).days

    def __unicode__(self):
        return '#%0#5d' % self.pk

    def diff(self, other_payment):
        changes = {}
        for attr in [
                'amount', 'due_date', 'status', 'customer', 'provider',
                'proforma', 'invoice', 'visible', 'currency',
                'currency_rate_date'
        ]:
            if not hasattr(other_payment, attr) or not hasattr(self, attr):
                continue

            current = getattr(self, attr, None)
            other = getattr(other_payment, attr, None)

            if current != other:
                changes[attr] = {'from': current, 'to': other}

        return changes
Пример #26
0
class Relationship(ManageableModel):
    start_date = models.DateTimeField(default=now)
    end_date = models.DateTimeField(null=True, blank=True)
    effective_start_date = models.DateTimeField(null=True, blank=True)
    effective_end_date = models.DateTimeField(null=True, blank=True)
    review_date = models.DateTimeField(null=True, blank=True)

    suspended_until = models.DateTimeField(null=True, blank=True)

    comment = models.TextField(blank=True)

    dependent_on = models.ForeignKey('self', null=True, blank=True)

    state = FSMField(max_length=16, choices=RELATIONSHIP_STATE_CHOICES, db_index=True, protected=True)
    suspended = FSMBooleanField(db_index=True, default=False, protected=True)

    delayed_save = GenericRelation(DelayedSave)

    class Meta:
        abstract = True

    def schedule_resave(self):
        now = timezone.now()
        dates = [self.start_date,
                 self.end_date,
                 self.effective_start_date,
                 self.effective_end_date,
                 self.review_date,
                 self.suspended_until]
        dates = sorted(d for d in dates if d and d > now)

        if dates:
            try:
                delayed_save = self.delayed_save.get()
            except DelayedSave.DoesNotExist:
                delayed_save = DelayedSave(object=self)
            delayed_save.when = dates[0]
            delayed_save.save()
        elif self.delayed_save.exists():
            self.delayed_save.get().delete()

    @transition(field=suspended, source=True, target=False)
    def unsuspend(self):
        self.suspended_until = None
        if self.state == 'suspended':
            self._unsuspend_state()

    @transition(field=suspended, source=False, target=True)
    def suspend(self, until=None):
        self.suspended_until = until
        if self.state == 'active':
            self._suspend_state()

    @transition(field=state, source='suspended', target='active')
    def _unsuspend_state(self):
        pass

    @transition(field=state, source='active', target='suspended')
    def _suspend_state(self):
        pass

    @transition(field=state, source='offered', target=RETURN_VALUE(),
                permission=is_owning_identity)
    def accept(self):
        return self._time_has_passed(now_active=True)

    @transition(field=state, source='offered', target='rejected',
                permission=is_owning_identity)
    def reject(self):
        pass

    @transition(field=state, source='*', target=RETURN_VALUE())
    def _time_has_passed(self, now_active=False):
        start_date = self.effective_start_date or self.start_date
        end_date = self.effective_end_date or self.end_date
        now = timezone.now()

        if self.state == 'suspended' and self.suspended_until and self.suspended_until < now:
            self.unsuspend()

        if now_active or self.state in {'forthcoming', 'active', 'historic', ''}:
            if start_date <= now and (not end_date or now <= end_date):
                return 'active' if not self.suspended else 'suspended'
            elif now < start_date:
                return 'forthcoming'
            elif end_date < now:
                return 'historic'
        else:
            return self.state

    def save(self, *args, **kwargs):
        self._time_has_passed()
        super().save(*args, **kwargs)
        self.schedule_resave()
Пример #27
0
class AbstractUpload(models.Model):
    """
    Abstract model for managing TUS uploads
    """
    guid = models.UUIDField(_('GUID'), default=uuid.uuid4, unique=True)

    state = FSMField(default=states.INITIAL)

    upload_offset = models.BigIntegerField(default=0)
    upload_length = models.BigIntegerField(default=-1)

    upload_metadata = JSONField(
        load_kwargs={'object_pairs_hook': collections.OrderedDict})

    filename = models.CharField(max_length=255, blank=True)

    temporary_file_path = models.CharField(max_length=4096, null=True)

    expires = models.DateTimeField(null=True, blank=True)

    class Meta:
        abstract = True

    def clean_fields(self, exclude=None):
        super(AbstractUpload, self).clean_fields(exclude=exclude)
        if self.upload_offset < 0:
            raise ValidationError(_('upload_offset should be >= 0.'))

    def write_data(self, bytes, chunk_size):
        num_bytes_written = write_bytes_to_file(self.temporary_file_path,
                                                self.upload_offset,
                                                bytes,
                                                makedirs=True)

        if num_bytes_written > 0:
            self.upload_offset += num_bytes_written
            self.save()

    def delete(self, *args, **kwargs):
        if self.temporary_file_path and os.path.exists(
                self.temporary_file_path):
            os.remove(self.temporary_file_path)
        super(AbstractUpload, self).delete(*args, **kwargs)

    def generate_filename(self):
        return os.path.join('{}.bin'.format(uuid.uuid4()))

    def save(self,
             force_insert=False,
             force_update=False,
             using=None,
             update_fields=None):
        if not self.filename:
            self.filename = self.generate_filename()
        return super(AbstractUpload, self).save(force_insert=force_insert,
                                                force_update=force_update,
                                                using=using,
                                                update_fields=update_fields)

    def is_complete(self):
        return self.upload_offset == self.upload_length

    def temporary_file_exists(self):
        return self.temporary_file_path and os.path.isfile(
            self.temporary_file_path)

    def _temporary_file_exists(self):
        return self.temporary_file_exists()

    def get_or_create_temporary_file(self):
        if not self.temporary_file_path:
            fd, path = tempfile.mkstemp(prefix="tus-upload-")
            os.close(fd)
            self.temporary_file_path = path
            self.save()
        assert os.path.isfile(self.temporary_file_path)
        return self.temporary_file_path

    @transition(field=state,
                source=states.INITIAL,
                target=states.RECEIVING,
                conditions=[_temporary_file_exists])
    def start_receiving(self):
        """
        State transition to indicate the first file chunk has been received successfully
        """
        # Trigger signal
        signals.receiving.send(sender=self.__class__, instance=self)

    @transition(field=state,
                source=states.RECEIVING,
                target=states.SAVING,
                conditions=[is_complete])
    def start_saving(self):
        """
        State transition to indicate that the upload is complete, and that the temporary file will be transferred to
          its final destination.
        """
        # Trigger signal
        signals.saving.send(sender=self.__class__, instance=self)

    @transition(field=state, source=states.SAVING, target=states.DONE)
    def finish(self):
        """
        State transition to indicate the upload is ready and the file is ready for access
        """
        # Trigger signal
        signals.finished.send(sender=self.__class__, instance=self)
Пример #28
0
class TPMVisit(SoftDeleteMixin, TimeStampedModel, models.Model):

    DRAFT = 'draft'
    ASSIGNED = 'assigned'
    CANCELLED = 'cancelled'
    ACCEPTED = 'tpm_accepted'
    REJECTED = 'tpm_rejected'
    REPORTED = 'tpm_reported'
    REPORT_REJECTED = 'tpm_report_rejected'
    UNICEF_APPROVED = 'unicef_approved'

    STATUSES = Choices(
        (DRAFT, _('Draft')),
        (ASSIGNED, _('Assigned')),
        (CANCELLED, _('Cancelled')),
        (ACCEPTED, _('TPM Accepted')),
        (REJECTED, _('TPM Rejected')),
        (REPORTED, _('TPM Reported')),
        (REPORT_REJECTED, _('Sent Back to TPM')),
        (UNICEF_APPROVED, _('UNICEF Approved')),
    )

    STATUSES_DATES = {
        STATUSES.draft: 'date_created',
        STATUSES.assigned: 'date_of_assigned',
        STATUSES.cancelled: 'date_of_cancelled',
        STATUSES.tpm_accepted: 'date_of_tpm_accepted',
        STATUSES.tpm_rejected: 'date_of_tpm_rejected',
        STATUSES.tpm_reported: 'date_of_tpm_reported',
        STATUSES.tpm_report_rejected: 'date_of_tpm_report_rejected',
        STATUSES.unicef_approved: 'date_of_unicef_approved',
    }

    author = models.ForeignKey(settings.AUTH_USER_MODEL,
                               on_delete=models.SET_NULL,
                               blank=True,
                               null=True)

    tpm_partner = models.ForeignKey(
        TPMPartner,
        verbose_name=_('TPM Vendor'),
        null=True,
        on_delete=models.CASCADE,
    )

    status = FSMField(verbose_name=_('Status'),
                      max_length=20,
                      choices=STATUSES,
                      default=STATUSES.draft,
                      protected=True)

    # UNICEF cancelled visit
    cancel_comment = models.TextField(verbose_name=_('Cancel Comment'),
                                      blank=True)
    # TPM rejected visit
    reject_comment = models.TextField(verbose_name=_('Reason for Rejection'),
                                      blank=True)
    approval_comment = models.TextField(verbose_name=_('Approval Comments'),
                                        blank=True)

    report_attachments = GenericRelation(Attachment,
                                         verbose_name=_('Visit Report'),
                                         blank=True)

    visit_information = models.TextField(verbose_name=_('Visit Information'),
                                         blank=True)

    date_of_assigned = models.DateField(blank=True,
                                        null=True,
                                        verbose_name=_('Date of Assigned'))
    date_of_cancelled = models.DateField(blank=True,
                                         null=True,
                                         verbose_name=_('Date of Cancelled'))
    date_of_tpm_accepted = models.DateField(
        blank=True, null=True, verbose_name=_('Date of TPM Accepted'))
    date_of_tpm_rejected = models.DateField(
        blank=True, null=True, verbose_name=_('Date of TPM Rejected'))
    date_of_tpm_reported = models.DateField(
        blank=True, null=True, verbose_name=_('Date of TPM Reported'))
    date_of_tpm_report_rejected = models.DateField(
        blank=True, null=True, verbose_name=_('Date of Sent Back to TPM'))
    date_of_unicef_approved = models.DateField(
        blank=True, null=True, verbose_name=_('Date of UNICEF Approved'))

    tpm_partner_focal_points = models.ManyToManyField(
        TPMPartnerStaffMember,
        verbose_name=_('TPM Focal Points'),
        related_name='tpm_visits',
        blank=True)

    tpm_partner_tracker = FieldTracker(fields=[
        'tpm_partner',
    ])

    class Meta:
        ordering = ('id', )
        verbose_name = _('TPM Visit')
        verbose_name_plural = _('TPM Visits')

    @property
    def date_created(self):
        return self.created.date()

    @property
    def status_date(self):
        return getattr(self, self.STATUSES_DATES[self.status])

    @property
    def reference_number(self):
        return '{}/{}/{}/TPM'.format(
            connection.tenant.country_short_code or '',
            self.created.year,
            self.id,
        )

    @property
    def start_date(self):
        # TODO: Rewrite to reduce number of SQL queries.
        return self.tpm_activities.aggregate(models.Min('date'))['date__min']

    @property
    def end_date(self):
        # TODO: Rewrite to reduce number of SQL queries.
        return self.tpm_activities.aggregate(models.Max('date'))['date__max']

    @property
    def unicef_focal_points(self):
        return set(
            itertools.chain(*map(lambda a: a.unicef_focal_points.all(),
                                 self.tpm_activities.all())))

    @property
    def unicef_focal_points_with_emails(self):
        return list(
            filter(lambda u: u.email and u.is_active,
                   self.unicef_focal_points))

    @property
    def unicef_focal_points_and_pme(self):
        users = self.unicef_focal_points_with_emails
        if self.author and self.author.is_active and self.author.email:
            users += [self.author]

        return users

    def __str__(self):
        return 'Visit ({} to {} at {} - {})'.format(
            self.tpm_partner, ', '.join(
                filter(
                    lambda x: x,
                    self.tpm_activities.values_list('partner__name',
                                                    flat=True))),
            self.start_date, self.end_date)

    def get_mail_context(self,
                         user=None,
                         include_token=False,
                         include_activities=True):
        object_url = self.get_object_url(user=user,
                                         include_token=include_token)

        activities = self.tpm_activities.all()
        interventions = set(a.intervention.title for a in activities
                            if a.intervention)
        partner_names = set(a.partner.name for a in activities)
        context = {
            'reference_number': self.reference_number,
            'tpm_partner': self.tpm_partner.name if self.tpm_partner else '-',
            'multiple_tpm_activities': activities.count() > 1,
            'object_url': object_url,
            'partners': ', '.join(partner_names),
            'interventions': ', '.join(interventions),
        }

        if include_activities:
            context['tpm_activities'] = [
                a.get_mail_context(user=user, include_visit=False)
                for a in activities
            ]

        return context

    def _send_email(self,
                    recipients,
                    template_name,
                    context=None,
                    user=None,
                    include_token=False,
                    **kwargs):
        context = context or {}

        base_context = {
            'visit': self.get_mail_context(user=user,
                                           include_token=include_token),
            'environment': get_environment(),
        }
        base_context.update(context)
        context = base_context

        if isinstance(recipients, str):
            recipients = [
                recipients,
            ]
        else:
            recipients = list(recipients)

        # assert recipients
        if recipients:
            send_notification_with_template(
                recipients=recipients,
                template_name=template_name,
                context=context,
            )

    def _get_unicef_focal_points_as_email_recipients(self):
        return list(
            map(lambda u: u.email, self.unicef_focal_points_with_emails))

    def _get_unicef_focal_points_and_pme_as_email_recipients(self):
        return list(map(lambda u: u.email, self.unicef_focal_points_and_pme))

    def _get_tpm_focal_points_as_email_recipients(self):
        return list(
            self.tpm_partner_focal_points.filter(
                user__email__isnull=False,
                user__is_active=True).values_list('user__email', flat=True))

    @transition(
        status,
        source=[STATUSES.draft, STATUSES.tpm_rejected],
        target=STATUSES.assigned,
        conditions=[
            TPMVisitAssignRequiredFieldsCheck.as_condition(),
            ValidateTPMVisitActivities.as_condition(),
        ],
        permission=has_action_permission(action='assign'),
        custom={
            'name':
            lambda obj: _('Re-assign')
            if obj.status == TPMVisit.STATUSES.tpm_rejected else _('Assign')
        })
    def assign(self):
        self.date_of_assigned = timezone.now()

        if self.tpm_partner.email:
            self._send_email(
                self.tpm_partner.email,
                'tpm/visit/assign',
                cc=self._get_unicef_focal_points_and_pme_as_email_recipients())

        for staff_member in self.tpm_partner_focal_points.filter(
                user__email__isnull=False, user__is_active=True):
            self._send_email(
                staff_member.user.email,
                'tpm/visit/assign_staff_member',
                context={'recipient': staff_member.user.get_full_name()},
                user=staff_member.user,
                include_token=True)

    @transition(status,
                source=[
                    STATUSES.draft,
                    STATUSES.assigned,
                    STATUSES.tpm_accepted,
                    STATUSES.tpm_rejected,
                    STATUSES.tpm_reported,
                    STATUSES.tpm_report_rejected,
                ],
                target=STATUSES.cancelled,
                permission=has_action_permission(action='cancel'),
                custom={
                    'serializer': TPMVisitCancelSerializer,
                    'name': _('Cancel Visit')
                })
    def cancel(self, cancel_comment):
        self.cancel_comment = cancel_comment
        self.date_of_cancelled = timezone.now()

    @transition(status,
                source=[STATUSES.assigned],
                target=STATUSES.tpm_rejected,
                permission=has_action_permission(action='reject'),
                custom={'serializer': TPMVisitRejectSerializer})
    def reject(self, reject_comment):
        self.date_of_tpm_rejected = timezone.now()
        self.reject_comment = reject_comment

        for recipient in self.unicef_focal_points_and_pme:
            self._send_email(
                recipient.email,
                'tpm/visit/reject',
                cc=self._get_tpm_focal_points_as_email_recipients(),
                context={'recipient': recipient.get_full_name()},
                user=recipient,
            )

    @transition(status,
                source=[STATUSES.assigned],
                target=STATUSES.tpm_accepted,
                permission=has_action_permission(action='accept'))
    def accept(self):
        self.date_of_tpm_accepted = timezone.now()

    @transition(status,
                source=[STATUSES.tpm_accepted, STATUSES.tpm_report_rejected],
                target=STATUSES.tpm_reported,
                conditions=[
                    TPMVisitReportValidations.as_condition(),
                ],
                permission=has_action_permission(action='send_report'),
                custom={'name': _('Submit Report')})
    def send_report(self):
        self.date_of_tpm_reported = timezone.now()

        for recipient in self.unicef_focal_points_and_pme:
            self._send_email(
                recipient.email,
                'tpm/visit/report',
                cc=self._get_tpm_focal_points_as_email_recipients(),
                context={'recipient': recipient.get_full_name()},
                user=recipient,
            )

    @transition(status,
                source=[STATUSES.tpm_reported],
                target=STATUSES.tpm_report_rejected,
                permission=has_action_permission(action='reject_report'),
                custom={
                    'serializer': TPMVisitRejectSerializer,
                    'name': _('Send back to TPM')
                })
    def reject_report(self, reject_comment):
        self.date_of_tpm_report_rejected = timezone.now()
        TPMVisitReportRejectComment.objects.create(
            reject_reason=reject_comment, tpm_visit=self)

        for staff_user in self.tpm_partner_focal_points.filter(
                user__email__isnull=False, user__is_active=True):
            self._send_email(
                [staff_user.user.email],
                'tpm/visit/report_rejected',
                context={'recipient': staff_user.user.get_full_name()},
                user=staff_user.user)

    @transition(status,
                source=[STATUSES.tpm_reported],
                target=STATUSES.unicef_approved,
                custom={'serializer': TPMVisitApproveSerializer},
                permission=has_action_permission(action='approve'))
    def approve(self,
                mark_as_programmatic_visit=None,
                approval_comment=None,
                notify_focal_point=True,
                notify_tpm_partner=True):
        mark_as_programmatic_visit = mark_as_programmatic_visit or []

        self.tpm_activities.filter(id__in=mark_as_programmatic_visit).update(
            is_pv=True)

        self.date_of_unicef_approved = timezone.now()
        if notify_focal_point:
            for recipient in self.unicef_focal_points_with_emails:
                self._send_email(
                    recipient.email,
                    'tpm/visit/approve_report',
                    context={'recipient': recipient.get_full_name()},
                    user=recipient)

        if notify_tpm_partner:
            # TODO: Generate report as PDF attachment.
            for staff_user in self.tpm_partner_focal_points.filter(
                    user__email__isnull=False, user__is_active=True):
                self._send_email(
                    [
                        staff_user.user.email,
                    ],
                    'tpm/visit/approve_report_tpm',
                    context={'recipient': staff_user.user.get_full_name()},
                    user=staff_user.user)

        if approval_comment:
            self.approval_comment = approval_comment

    def get_object_url(self, **kwargs):
        return build_frontend_url('tpm', 'visits', self.id, 'details',
                                  **kwargs)
Пример #29
0
class DeliveryGroup(models.Model):
    """Represents a single shipment.

    A single order can consist of multiple shipment groups.
    """

    status = FSMField(max_length=32,
                      default=GroupStatus.NEW,
                      choices=GroupStatus.CHOICES,
                      protected=True)
    order = models.ForeignKey(Order,
                              related_name='groups',
                              editable=False,
                              on_delete=models.CASCADE)
    shipping_method_name = models.CharField(max_length=255,
                                            null=True,
                                            default=None,
                                            blank=True,
                                            editable=False)
    tracking_number = models.CharField(max_length=255, default='', blank=True)
    last_updated = models.DateTimeField(null=True, auto_now=True)

    def __str__(self):
        return pgettext_lazy('Shipment group str', 'Shipment #%s') % self.pk

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, list(self))

    def __iter__(self):
        return iter(self.lines.all())

    @transition(field=status, source=GroupStatus.NEW, target=GroupStatus.NEW)
    def process(self, cart_lines, discounts=None):
        process_delivery_group(self, cart_lines, discounts)

    @transition(field=status,
                source=GroupStatus.NEW,
                target=GroupStatus.SHIPPED)
    def ship(self, tracking_number=''):
        ship_delivery_group(self, tracking_number)

    @transition(field=status,
                source=[GroupStatus.NEW, GroupStatus.SHIPPED],
                target=GroupStatus.CANCELLED)
    def cancel(self):
        cancel_delivery_group(self)

    def get_total_quantity(self):
        return sum([line.quantity for line in self])

    def is_shipping_required(self):
        return any([line.is_shipping_required for line in self.lines.all()])

    def can_ship(self):
        return self.is_shipping_required() and self.status == GroupStatus.NEW

    def can_cancel(self):
        return self.status != GroupStatus.CANCELLED

    def can_edit_lines(self):
        return self.status not in {GroupStatus.CANCELLED, GroupStatus.SHIPPED}

    def get_total(self):
        subtotals = [line.get_total() for line in self]
        if not subtotals:
            raise AttributeError(
                'Calling get_total() on an empty shipment group')
        return sum(subtotals[1:], subtotals[0])
Пример #30
0
class CourseState(TimeStampedModel, ChangedByMixin):
    """ Publisher Workflow Course State Model. """

    name = FSMField(default=CourseStateChoices.Draft,
                    choices=CourseStateChoices.choices)
    approved_by_role = models.CharField(blank=True,
                                        null=True,
                                        max_length=63,
                                        choices=PublisherUserRole.choices)
    owner_role = models.CharField(max_length=63,
                                  choices=PublisherUserRole.choices)
    course = models.OneToOneField(Course, related_name='course_state')
    owner_role_modified = models.DateTimeField(auto_now_add=True,
                                               null=True,
                                               blank=True)
    marketing_reviewed = models.BooleanField(default=False)

    history = HistoricalRecords()

    # course team status
    Draft = _('Draft')
    SubmittedForMarketingReview = _('Submitted for Marketing Review')
    ApprovedByCourseTeam = _('Approved by Course Team')
    AwaitingCourseTeamReview = _('Awaiting Course Team Review')

    # internal user status
    NotAvailable = _('N/A')
    AwaitingMarketingReview = _('Awaiting Marketing Review')
    ApprovedByMarketing = _('Approved by Marketing')

    def __str__(self):
        return self.get_name_display()

    def can_send_for_review(self):
        """
        Validate minimum required fields before sending for review.
        """
        course = self.course
        return all([
            course.title, course.number, course.short_description,
            course.full_description,
            course.organizations.first(), course.level_type,
            course.expected_learnings, course.primary_subject, course.image,
            course.course_team_admin
        ])

    @transition(field=name, source='*', target=CourseStateChoices.Draft)
    def draft(self):
        # TODO: send email etc.
        pass

    @transition(field=name,
                source=CourseStateChoices.Draft,
                target=CourseStateChoices.Review,
                conditions=[can_send_for_review])
    def review(self):
        # TODO: send email etc.
        pass

    @transition(field=name,
                source=CourseStateChoices.Review,
                target=CourseStateChoices.Approved)
    def approved(self):
        # TODO: send email etc.
        pass

    def change_state(self, state, user, site=None):
        """
        Change course workflow state and ownership also send emails if required.
        """
        is_notifications_enabled = waffle.switch_is_active(
            'enable_publisher_email_notifications')
        if state == CourseStateChoices.Draft:
            self.draft()
        elif state == CourseStateChoices.Review:
            user_role = self.course.course_user_roles.get(user=user)
            if user_role.role == PublisherUserRole.MarketingReviewer:
                self.change_owner_role(PublisherUserRole.CourseTeam)
                self.marketing_reviewed = True
            elif user_role.role == PublisherUserRole.CourseTeam:
                self.change_owner_role(PublisherUserRole.MarketingReviewer)
                if is_notifications_enabled:
                    emails.send_email_for_seo_review(self.course, site)

            self.review()

            if is_notifications_enabled:
                emails.send_email_for_send_for_review(self.course, user, site)

        elif state == CourseStateChoices.Approved:
            user_role = self.course.course_user_roles.get(user=user)
            self.approved_by_role = user_role.role
            self.marketing_reviewed = True
            self.approved()

            if is_notifications_enabled:
                emails.send_email_for_mark_as_reviewed(self.course, user, site)

        self.save()

    @property
    def is_approved(self):
        """ Check that course is approved or not."""
        return self.name == CourseStateChoices.Approved

    def change_owner_role(self, role):
        """
        Change ownership role.
        """
        self.owner_role = role
        self.owner_role_modified = timezone.now()
        self.save()

    @property
    def is_draft(self):
        """ Check that course is in Draft state or not."""
        return self.name == CourseStateChoices.Draft

    @property
    def is_in_review(self):
        """ Check that course is in Review state or not."""
        return self.name == CourseStateChoices.Review

    @property
    def course_team_status(self):
        if self.is_draft and self.owner_role == PublisherUserRole.CourseTeam and not self.marketing_reviewed:
            return self.Draft
        elif self.owner_role == PublisherUserRole.MarketingReviewer:
            return self.SubmittedForMarketingReview
        elif self.owner_role == PublisherUserRole.CourseTeam and self.is_approved:
            return self.ApprovedByCourseTeam
        elif self.marketing_reviewed and self.owner_role == PublisherUserRole.CourseTeam:
            return self.AwaitingCourseTeamReview

    @property
    def internal_user_status(self):
        if self.is_draft and self.owner_role == PublisherUserRole.CourseTeam:
            return self.NotAvailable
        elif self.owner_role == PublisherUserRole.MarketingReviewer and (
                self.is_in_review or self.is_draft):
            return self.AwaitingMarketingReview
        elif self.marketing_reviewed:
            return self.ApprovedByMarketing