forms.py 9.87 KB
Newer Older
1 2 3 4
"""
go/forms.py
"""

5
# Future Imports
David Haynes's avatar
David Haynes committed
6 7
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
8

9
# Python stdlib Imports
David Haynes's avatar
David Haynes committed
10
from datetime import datetime, timedelta
11 12
from six.moves import urllib

13
# Django Imports
14
from django.core.exceptions import ValidationError
15 16 17
from django.forms import (BooleanField, CharField, ChoiceField, DateTimeField,
                          ModelForm, RadioSelect, SlugField, Textarea,
                          TextInput, URLField, URLInput)
Matthew Rodgers's avatar
Matthew Rodgers committed
18
from django.utils import timezone
David Haynes's avatar
David Haynes committed
19
from django.utils.safestring import mark_safe
20 21 22 23 24

# App Imports
from go.models import URL, RegisteredUser

# Other Imports
25
from bootstrap3_datetime.widgets import DateTimePicker
David Haynes's avatar
David Haynes committed
26 27 28 29 30
from crispy_forms.bootstrap import (Accordion, AccordionGroup, PrependedText,
                                    StrictButton)
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Div, Field, Fieldset, Layout, Submit

31

32
class URLForm(ModelForm):
33
    """
David Haynes's avatar
David Haynes committed
34
    The form that is used in URL creation.
35
    """
36

David Haynes's avatar
David Haynes committed
37 38
    # target -------------------------------------------------------------------

39
    def clean_target(self):
David Haynes's avatar
David Haynes committed
40 41 42 43
        """
        Prevent redirect loop links
        """

David Haynes's avatar
David Haynes committed
44
        # get the entered target link
45
        target = self.cleaned_data.get('target')
David Haynes's avatar
David Haynes committed
46

47 48
        try:
            final_url = urllib.request.urlopen(target).geturl()
49 50
        # if visiting the provided url results in an HTTP error, or redirects
        # to a page that results in an HTTP error
51 52 53
        except urllib.error.URLError as e:
            # to permit users to enter sites that return most errors, but
            # prevent them from entering sites that result in an HTTP 300 error
54
            if any(int(str(e)[11:14]) == errorNum for errorNum in range(300, 308)):
55
                raise ValidationError("Link results in a 300 error")
56 57
            else:
                final_url = ""
David Haynes's avatar
David Haynes committed
58 59 60 61 62 63 64 65 66 67 68

        # Commented out as this check cannont properly be tested since we cannot
        # dynamically generate request.META.get('HTTP_HOST')

        # # if the host (go.gmu.edu) is in the entered target link or where it
        # # redirects
        # if self.host in final_url or self.host in target:
        #     raise ValidationError("You can't make a Go link to Go silly!")
        # else:
        #     return target
        return target
69

70
    # Custom target URL field
71
    target = URLField(
72
        required=True,
73
        label='Long URL (Required)',
74
        max_length=1000,
75
        widget=URLInput(attrs={
76
            'placeholder': 'https://yoursite.com/'
77 78 79
        })
    )

David Haynes's avatar
David Haynes committed
80 81
    # short --------------------------------------------------------------------

82
    def unique_short(value):
David Haynes's avatar
David Haynes committed
83 84 85 86
        """
        Check to make sure the short url has not been used
        """

87
        try:
David Haynes's avatar
David Haynes committed
88
            # if we're able to get a URL with the same short url
89
            URL.objects.get(short__iexact=value)
90
        except URL.DoesNotExist as ex:
91
            return
David Haynes's avatar
David Haynes committed
92

David Haynes's avatar
David Haynes committed
93
        # then raise a ValidationError
94 95 96
        raise ValidationError('Short url already exists.')

    # Custom short-url field with validators.
97
    short = SlugField(
98 99
        required=False,
        label='Short URL (Optional)',
100
        widget=TextInput(),
101 102 103
        validators=[unique_short],
        max_length=20,
        min_length=3,
104 105
    )

David Haynes's avatar
David Haynes committed
106 107 108
    # expires ------------------------------------------------------------------

    # Define some string date standards
109 110 111
    DAY = '1 Day'
    WEEK = '1 Week'
    MONTH = '1 Month'
112
    CUSTOM = 'Custom Date'
113 114
    NEVER = 'Never'

David Haynes's avatar
David Haynes committed
115
    # Define a tuple of string date standards to be used as our date choices
116 117 118 119 120
    EXPIRATION_CHOICES = (
        (DAY, DAY),
        (WEEK, WEEK),
        (MONTH, MONTH),
        (NEVER, NEVER),
121
        (CUSTOM, CUSTOM),
122 123
    )

Matthew Rodgers's avatar
Matthew Rodgers committed
124
    # Add preset expiration choices.
125
    expires = ChoiceField(
126 127 128 129
        required=True,
        label='Expiration (Required)',
        choices=EXPIRATION_CHOICES,
        initial=NEVER,
130
        widget=RadioSelect(),
131 132
    )

Matthew Rodgers's avatar
Matthew Rodgers committed
133
    def valid_date(value):
David Haynes's avatar
David Haynes committed
134 135 136 137
        """
        Check if the selected date is a valid date
        """

David Haynes's avatar
David Haynes committed
138
        # a valid date is one that is greater than today
Matthew Rodgers's avatar
Matthew Rodgers committed
139 140
        if value > timezone.now():
            return
David Haynes's avatar
David Haynes committed
141
        # raise a ValidationError if the date is invalid
Matthew Rodgers's avatar
Matthew Rodgers committed
142
        else:
143
            raise ValidationError('Date must be after today.')
Matthew Rodgers's avatar
Matthew Rodgers committed
144 145


Matthew Rodgers's avatar
Matthew Rodgers committed
146
    # Add a custom expiration choice.
147
    expires_custom = DateTimeField(
148 149 150 151 152 153
        required=False,
        label='Custom Date',
        input_formats=['%m-%d-%Y'],
        validators=[valid_date],
        initial=lambda: datetime.now() + timedelta(days=1),
        widget=DateTimePicker(
154 155 156 157 158 159
            options={
                "format": "MM-DD-YYYY",
                "pickTime": False,
            },
            icon_attrs={
                "class": "fa fa-calendar",
David Haynes's avatar
David Haynes committed
160 161
            },
        )
162 163
    )

164
    def __init__(self, *args, **kwargs):
David Haynes's avatar
David Haynes committed
165 166 167 168
        """
        On initialization of the form, crispy forms renders this layout
        """

169 170
        # Grab that host info
        self.host = kwargs.pop('host', None)
171
        super(URLForm, self).__init__(*args, **kwargs)
172
        # Define the basics for crispy-forms
173
        self.helper = FormHelper()
174
        self.helper.form_method = 'POST'
175

176
        # Some xtra vars for form css purposes
177 178 179 180
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-1'
        self.helper.field_class = 'col-md-6'

181
        # The main "layout" defined
182
        self.helper.layout = Layout(
183 184
            Fieldset('',
            #######################
185
                Accordion(
186
                    # Step 1: Long URL
187 188 189 190
                    AccordionGroup('Step 1: Long URL',
                        Div(
                            HTML("""
                                <h4>Paste the URL you would like to shorten:</h4>
191
                                <br />"""),
192
                            'target',
193 194 195
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),
196 197

                    # Step 2: Short URL
198 199 200 201
                    AccordionGroup('Step 2: Short URL',
                        Div(
                            HTML("""
                                <h4>Create a custom Go address:</h4>
202 203
                                <br />"""),
                            PrependedText(
204
                            'short', 'https://go.gmu.edu/', template='crispy/customPrepended.html'),
205 206 207
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html',),
208 209

                    # Step 3: Expiration
210 211 212 213
                    AccordionGroup('Step 3: URL Expiration',
                        Div(
                            HTML("""
                                <h4>Set when you would like your Go address to expire:</h4>
214
                                <br />"""),
215
                            'expires',
216
                            Field('expires_custom', template="crispy/customDateField.html"),
217 218 219
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),
220

221 222
                # FIN
                template='crispy/accordian.html'),
223 224 225 226
            #######################
            HTML("""
                <br />"""),
            StrictButton('Shorten', css_class="btn btn-primary btn-md col-md-4", type='submit')))
227

228
    class Meta:
David Haynes's avatar
David Haynes committed
229 230 231 232
        """
        Metadata about this ModelForm
        """

David Haynes's avatar
David Haynes committed
233
        # what model this form is for
234
        model = URL
David Haynes's avatar
David Haynes committed
235
        # what attributes are included
236
        fields = ['target']
237

238
class SignupForm(ModelForm):
239
    """
David Haynes's avatar
David Haynes committed
240
    The form that is used when a user is signing up to be a RegisteredUser
241
    """
242

David Haynes's avatar
David Haynes committed
243
    # The full name of the RegisteredUser
244
    full_name = CharField(
245 246 247
        required=True,
        label='Full Name (Required)',
        max_length=100,
248
        widget=TextInput(),
249
    )
David Haynes's avatar
David Haynes committed
250 251

    # The RegisteredUser's chosen organization
252
    organization = CharField(
253 254 255
        required=True,
        label='Organization (Required)',
        max_length=100,
256
        widget=TextInput(),
257
    )
David Haynes's avatar
David Haynes committed
258 259

    # The RegisteredUser's reason for signing up to us Go
260
    description = CharField(
261 262 263
        required=False,
        label='Description (Optional)',
        max_length=200,
264
        widget=Textarea(),
265
    )
David Haynes's avatar
David Haynes committed
266

267
    # A user becomes registered when they agree to the TOS
268
    registered = BooleanField(
269
        required=True,
David Haynes's avatar
David Haynes committed
270 271
        # ***Need to replace lower url with production URL***
        # ie. go.gmu.edu/about#terms
272 273 274
        label=mark_safe(
            'Do you accept the <a href="http://127.0.0.1:8000/about#terms">Terms of Service</a>?'
        ),
275 276
    )

277
    def __init__(self, request, *args, **kwargs):
David Haynes's avatar
David Haynes committed
278 279 280 281 282 283
        """
        On initialization of the form, crispy forms renders this layout
        """

        # Necessary to call request in forms.py, is otherwise restricted to
        # views.py and models.py
284 285
        self.request = request
        super(SignupForm, self).__init__(*args, **kwargs)
David Haynes's avatar
David Haynes committed
286
        self.helper = FormHelper()
287 288 289 290 291
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-4'
        self.helper.field_class = 'col-md-6'

        self.helper.layout = Layout(
292
            Fieldset('',
293
                Div(
294
                    # Place in form fields
295 296 297 298
                    Div(
                        'full_name',
                        'organization',
                        'description',
299
                        'registered',
300 301 302
                        css_class='well'),

                    # Extras at bottom
303
                    StrictButton('Submit',css_class='btn btn-primary btn-md col-md-4', type='submit'),
304
                    css_class='col-md-6')))
David Haynes's avatar
David Haynes committed
305

306
    class Meta:
David Haynes's avatar
David Haynes committed
307 308 309 310
        """
        Metadata about this ModelForm
        """

David Haynes's avatar
David Haynes committed
311
        # what model this form is for
312
        model = RegisteredUser
David Haynes's avatar
David Haynes committed
313
        # what attributes are included
314
        fields = ['full_name', 'organization', 'description', 'registered']