models.py 4.47 KB
Newer Older
1 2
"""
go/models.py
3

David Haynes's avatar
David Haynes committed
4 5 6
The core of Go: define the business logic through classes that represent
tables containing structured data in the database.
"""
David Haynes's avatar
David Haynes committed
7 8 9
# Python stdlib Imports
import string

10
# Django Imports
Jean Michel Rouly's avatar
Jean Michel Rouly committed
11
from django.contrib.auth.models import User
12
from django.core.cache import cache
13
from django.db import models
14 15
from django.db.models.signals import post_save
from django.dispatch import receiver
16
from django.utils import timezone
17 18

# Other Imports
19
from hashids import Hashids  # http://hashids.org/python/
20

21
# generate the salt and initialize Hashids
22 23 24
# note: the Hashids library already implements several restrictions
# on character placement, including repeating or incrementing numbers,
# or placing curse word characters adjacent to one another
25
similar_chars = set(['b', 'G', '6',
26 27 28 29 30 31 32
                     'g', 'q',
                     'l', '1', 'I',
                     'S', '5',
                     'O', '0',])

alphanumerics = set(string.ascii_letters + string.digits)

33
link_chars = ''.join(alphanumerics - similar_chars)
34

35
HASHIDS = Hashids(
36
    salt="srct.gmu.edu", alphabet=(link_chars)
37
)
Jean Michel Rouly's avatar
Jean Michel Rouly committed
38

39
class RegisteredUser(models.Model):
40
    """
David Haynes's avatar
David Haynes committed
41
    This is simply a wrapper model for the User model which, if an object
David Haynes's avatar
David Haynes committed
42
    exists, indicates that that user is registered.
43
    """
44
    # Let's associate a User to this RegisteredUser
David Haynes's avatar
David Haynes committed
45
    user = models.OneToOneField(User, on_delete="cascade")
Jean Michel Rouly's avatar
Jean Michel Rouly committed
46

47
    # What is your name?
48
    full_name = models.CharField(
49 50
        blank=False,
        max_length=100,
51
    )
Jean Michel Rouly's avatar
Jean Michel Rouly committed
52

53
    # What organization are you associated with?
54
    organization = models.CharField(
55 56
        blank=False,
        max_length=100,
57 58
    )

59
    # Why do you want to use Go?
60
    description = models.TextField(blank=True)
61

62
    # Have you filled out the registration form?
63
    registered = models.BooleanField(default=False)
64

65
    # Are you approved to use Go?
66
    approved = models.BooleanField(default=False)
67

David Haynes's avatar
David Haynes committed
68 69 70
    # Is this User Blocked?
    blocked = models.BooleanField(default=False)

71
    def __str__(self):
David Haynes's avatar
David Haynes committed
72
        """
David Haynes's avatar
David Haynes committed
73
        String representation of this object.
David Haynes's avatar
David Haynes committed
74
        """
75 76 77
        return '<Registered User: %s - Approval Status: %s>' % (
            self.user, self.approved
        )
78 79 80

@receiver(post_save, sender=User)
def handle_regUser_creation(sender, instance, created, **kwargs):
David Haynes's avatar
David Haynes committed
81
    """
82
    When a post_save is called on a User object (and it is newly created), this
David Haynes's avatar
David Haynes committed
83
    is called to create an associated RegisteredUser.
David Haynes's avatar
David Haynes committed
84
    """
85 86 87
    if created:
        RegisteredUser.objects.create(user=instance)

88
class URL(models.Model):
David Haynes's avatar
David Haynes committed
89
    """
David Haynes's avatar
David Haynes committed
90 91 92
    This model represents a stored URL redirection rule. Each URL has an
    owner, target url, short identifier, click counter, and expiration
    date.
David Haynes's avatar
David Haynes committed
93
    """
94
    # Who is the owner of this Go link
David Haynes's avatar
David Haynes committed
95
    owner = models.ForeignKey(RegisteredUser, on_delete="cascade")
96
    # When was this link created?
David Haynes's avatar
David Haynes committed
97
    date_created = models.DateTimeField(default=timezone.now)
98

99
    # What is the target URL for this Go link
David Haynes's avatar
David Haynes committed
100
    target = models.URLField(max_length=1000)
101
    # What is the actual go link (short url) for this URL
David Haynes's avatar
David Haynes committed
102
    short = models.SlugField(max_length=20, primary_key=True)
103

104
    # how many people have visited this Go link
David Haynes's avatar
David Haynes committed
105
    clicks = models.IntegerField(default=0)
106
    # how many people have visited this Go link through the qr code
David Haynes's avatar
David Haynes committed
107
    qrclicks = models.IntegerField(default=0)
108
    # how many people have visited the go link through social media
David Haynes's avatar
David Haynes committed
109
    socialclicks = models.IntegerField(default=0)
110

111
    # does this Go link expire on a certain date
David Haynes's avatar
David Haynes committed
112
    expires = models.DateTimeField(blank=True, null=True)
113

114
    def __str__(self):
David Haynes's avatar
David Haynes committed
115
        """
David Haynes's avatar
David Haynes committed
116
        String representation of this object.
David Haynes's avatar
David Haynes committed
117
        """
118 119 120
        return '<Owner: %s - Target URL: %s>' % (
            self.owner.user, self.target
        )
121 122

    class Meta:
David Haynes's avatar
David Haynes committed
123
        """
David Haynes's avatar
David Haynes committed
124
        Meta information for this object.
David Haynes's avatar
David Haynes committed
125
        """
126
        # they should be ordered by their short links
127 128 129 130
        ordering = ['short']

    @staticmethod
    def generate_valid_short():
David Haynes's avatar
David Haynes committed
131 132 133 134
        """
        legacy method to ensure that generated short URL's are valid
        should be updated to be simpler
        """
135
        if cache.get("hashids_counter") is None:
136 137 138 139
            cache.set("hashids_counter", URL.objects.count())
        tries = 1
        while tries < 100:
            try:
Nicholas J Anderson's avatar
Nicholas J Anderson committed
140
                short = HASHIDS.encrypt(cache.get("hashids_counter"))
141 142
                tries += 1
                cache.incr("hashids_counter")
Nicholas J Anderson's avatar
Nicholas J Anderson committed
143
                URL.objects.get(short__iexact=short)
144
            except URL.DoesNotExist as ex:
145
                print(ex)
146 147
                return short
        return None