@@ -8,10 +8,10 @@ meta: Python code examples for the PositiveSmallIntegerField class used in the D
88
99
1010[ PositiveSmallIntegerField] ( https://github.com/django/django/blob/master/django/db/models/fields/__init__.py )
11- ([ documentation] ( https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.PositiveIntegerField ) )
11+ ([ documentation] ( https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.PositiveSmallIntegerField ) )
1212is a [ Django ORM] ( /django-orm.html ) mapping from your Python code to an
1313integer-type column in your [ relational database] ( /databases.html )
14- that is restricted to only positive values from 0 to 2147483647 .
14+ that is restricted to only positive values from 0 to 32767 .
1515
1616Note that ` PositiveSmallIntegerField ` is defined within the
1717[ django.db.models.fields] ( https://github.com/django/django/blob/master/django/db/models/fields/__init__.py )
@@ -348,3 +348,244 @@ def inlineformset_factory(parent_model, model, form=ModelForm,
348348```
349349
350350
351+ # # Example 4 from django-flexible-subscriptions
352+ [django- flexible- subscriptions](https:// github.com/ studybuffalo/ django- flexible- subscriptions)
353+ ([project documentation](https:// django- flexible- subscriptions.readthedocs.io/ en/ latest/ )
354+ and
355+ [PyPI package information](https:// pypi.org/ project/ django- flexible- subscriptions/ ))
356+ provides boilerplate code for adding subscription and recurrent billing
357+ to [Django](/ django.html) web applications. Various payment providers
358+ can be added on the back end to run the transactions.
359+
360+ The django- flexible- subscriptions project is open sourced under the
361+ [GNU General Public License v3.0](https:// github.com/ studybuffalo/ django- flexible- subscriptions/ blob/ master/ LICENSE ).
362+
363+ [** django- flexible- subscriptions / subscriptions / . / models.py** ](https:// github.com/ studybuffalo/ django- flexible- subscriptions/ blob/ master/ subscriptions/ ./ models.py)
364+
365+ ```python
366+ """ Models for the Flexible Subscriptions app."""
367+ from datetime import timedelta
368+ from uuid import uuid4
369+
370+ from django.contrib.auth import get_user_model
371+ from django.contrib.auth.models import Group
372+ from django.core.validators import MinValueValidator
373+ ~~ from django.db import models
374+ from django.utils.translation import ugettext_lazy as _
375+
376+
377+ # Convenience references for units for plan recurrence billing
378+ # ----------------------------------------------------------------------------
379+ ONCE = ' 0'
380+ SECOND = ' 1'
381+ MINUTE = ' 2'
382+ HOUR = ' 3'
383+ DAY = ' 4'
384+ WEEK = ' 5'
385+ MONTH = ' 6'
386+ YEAR = ' 7'
387+ RECURRENCE_UNIT_CHOICES = (
388+ (ONCE , ' once' ),
389+ (SECOND , ' second' ),
390+ (MINUTE , ' minute' ),
391+ (HOUR , ' hour' ),
392+ (DAY , ' day' ),
393+ (WEEK , ' week' ),
394+ (MONTH , ' month' ),
395+ (YEAR , ' year' ),
396+ )
397+ # ----------------------------------------------------------------------------
398+
399+ class PlanTag(models.Model):
400+ """ A tag for a subscription plan."""
401+ tag = models.CharField(
402+ help_text = _(' the tag name' ),
403+ max_length = 64 ,
404+ unique = True ,
405+ )
406+
407+ class Meta:
408+ ordering = (' tag' ,)
409+
410+ def __str__ (self ):
411+ return self .tag
412+
413+ class SubscriptionPlan(models.Model):
414+ """ Details for a subscription plan."""
415+ id = models.UUIDField(
416+ default = uuid4,
417+ editable = False ,
418+ primary_key = True ,
419+ verbose_name = ' ID' ,
420+ )
421+ plan_name = models.CharField(
422+ help_text = _(' the name of the subscription plan' ),
423+ max_length = 128 ,
424+ )
425+ plan_description = models.CharField(
426+ blank = True ,
427+ help_text = _(' a description of the subscription plan' ),
428+ max_length = 512 ,
429+ null = True ,
430+ )
431+ group = models.ForeignKey(
432+ Group,
433+ blank = True ,
434+ help_text = _(' the Django auth group for this plan' ),
435+ null = True ,
436+ on_delete = models.SET_NULL ,
437+ related_name = ' plans' ,
438+ )
439+ tags = models.ManyToManyField(
440+ PlanTag,
441+ blank = True ,
442+ help_text = _(' any tags associated with this plan' ),
443+ related_name = ' plans' ,
444+ )
445+ grace_period = models.PositiveIntegerField(
446+ default = 0 ,
447+ help_text = _(
448+ ' how many days after the subscription ends before the '
449+ ' subscription expires'
450+ ),
451+ )
452+
453+ class Meta:
454+ ordering = (' plan_name' ,)
455+ permissions = (
456+ (' subscriptions' , ' Can interact with subscription details' ),
457+ )
458+
459+ def __str__ (self ):
460+ return self .plan_name
461+
462+ def display_tags(self ):
463+ """ Displays tags as a string (truncates if more than 3)."""
464+ if self .tags.count() > 3 :
465+ return ' {} , ...' .format(
466+ ' , ' .join(tag.tag for tag in self .tags.all()[:3 ])
467+ )
468+
469+ return ' , ' .join(tag.tag for tag in self .tags.all()[:3 ])
470+
471+ class PlanCost(models.Model):
472+ """ Cost and frequency of billing for a plan."""
473+ id = models.UUIDField(
474+ default = uuid4,
475+ editable = False ,
476+ primary_key = True ,
477+ verbose_name = ' ID' ,
478+ )
479+ plan = models.ForeignKey(
480+ SubscriptionPlan,
481+ help_text = _(' the subscription plan for these cost details' ),
482+ on_delete = models.CASCADE ,
483+ related_name = ' costs' ,
484+ )
485+ ~~ recurrence_period = models.PositiveSmallIntegerField(
486+ ~~ default = 1 ,
487+ ~~ help_text = _(' how often the plan is billed (per recurrence unit)' ),
488+ ~~ validators = [MinValueValidator(1 )],
489+ ~~ )
490+ recurrence_unit = models.CharField(
491+ choices = RECURRENCE_UNIT_CHOICES ,
492+ default = MONTH ,
493+ max_length = 1 ,
494+ )
495+ cost = models.DecimalField(
496+ blank = True ,
497+ decimal_places = 4 ,
498+ help_text = _(' the cost per recurrence of the plan' ),
499+ max_digits = 19 ,
500+ null = True ,
501+ )
502+
503+ ~~ class Meta:
504+ ~~ ordering = (' recurrence_unit' , ' recurrence_period' , ' cost' ,)
505+
506+ @ property
507+ def display_recurrent_unit_text(self ):
508+ """ Converts recurrence_unit integer to text."""
509+ conversion = {
510+ ONCE : ' one-time' ,
511+ SECOND : ' per second' ,
512+ MINUTE : ' per minute' ,
513+ HOUR : ' per hour' ,
514+ DAY : ' per day' ,
515+ WEEK : ' per week' ,
516+ MONTH : ' per month' ,
517+ YEAR : ' per year' ,
518+ }
519+
520+ return conversion[self .recurrence_unit]
521+
522+ @ property
523+ def display_billing_frequency_text(self ):
524+ """ Generates human-readable billing frequency."""
525+ conversion = {
526+ ONCE : ' one-time' ,
527+ SECOND : {' singular' : ' per second' , ' plural' : ' seconds' },
528+ MINUTE : {' singular' : ' per minute' , ' plural' : ' minutes' },
529+ HOUR : {' singular' : ' per hour' , ' plural' : ' hours' },
530+ DAY : {' singular' : ' per day' , ' plural' : ' days' },
531+ WEEK : {' singular' : ' per week' , ' plural' : ' weeks' },
532+ MONTH : {' singular' : ' per month' , ' plural' : ' months' },
533+ YEAR : {' singular' : ' per year' , ' plural' : ' years' },
534+ }
535+
536+ if self .recurrence_unit == ONCE :
537+ return conversion[ONCE ]
538+
539+ ~~ if self .recurrence_period == 1 :
540+ ~~ return conversion[self .recurrence_unit][' singular' ]
541+
542+ ~~ return ' every {} {} ' .format(
543+ ~~ self .recurrence_period, conversion[self .recurrence_unit][' plural' ]
544+ ~~ )
545+
546+ def next_billing_datetime(self , current):
547+ """ Calculates next billing date for provided datetime.
548+
549+ Parameters:
550+ current (datetime): The current datetime to compare
551+ against.
552+
553+ Returns:
554+ datetime: The next time billing will be due.
555+ """
556+ ~~ if self .recurrence_unit == SECOND :
557+ ~~ return current + timedelta(seconds = self .recurrence_period)
558+
559+ ~~ if self .recurrence_unit == MINUTE :
560+ ~~ return current + timedelta(minutes = self .recurrence_period)
561+
562+ ~~ if self .recurrence_unit == HOUR :
563+ ~~ return current + timedelta(hours = self .recurrence_period)
564+
565+ ~~ if self .recurrence_unit == DAY :
566+ ~~ return current + timedelta(days = self .recurrence_period)
567+
568+ ~~ if self .recurrence_unit == WEEK :
569+ ~~ return current + timedelta(weeks = self .recurrence_period)
570+
571+ ~~ if self .recurrence_unit == MONTH :
572+ ~~ # Adds the average number of days per month as per:
573+ ~~ # http://en.wikipedia.org/wiki/Month#Julian_and_Gregorian_calendars
574+ ~~ # This handle any issues with months < 31 days and leap years
575+ ~~ return current + timedelta(
576+ ~~ days = 30.4368 * self .recurrence_period
577+ ~~ )
578+
579+ ~~ if self .recurrence_unit == YEAR :
580+ ~~ # Adds the average number of days per year as per:
581+ ~~ # http://en.wikipedia.org/wiki/Year#Calendar_year
582+ ~~ # This handle any issues with leap years
583+ ~~ return current + timedelta(
584+ ~~ days = 365.2425 * self .recurrence_period
585+ ~~ )
586+
587+ ~~ return None
588+
589+
590+ # # ... source file continues with no further relevant examples ...
591+ ```
0 commit comments