models.py 4.48 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

David Haynes's avatar
David Haynes committed
39

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

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

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

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

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

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

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

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

David Haynes's avatar
David Haynes committed
80

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

David Haynes's avatar
David Haynes committed
90

91
class URL(models.Model):
David Haynes's avatar
David Haynes committed
92
    """
David Haynes's avatar
David Haynes committed
93
94
95
    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
96
    """
97
    # Who is the owner of this Go link
David Haynes's avatar
David Haynes committed
98
    owner = models.ForeignKey(RegisteredUser, on_delete="cascade")
99
    # When was this link created?
David Haynes's avatar
David Haynes committed
100
    date_created = models.DateTimeField(default=timezone.now)
101

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

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

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

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

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

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