forms.py 12.9 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

# App Imports
22
from .models import URL, RegisteredUser
23
24

# 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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
class EditForm(URLForm):

    def __init__(self, *args, **kwargs):
        """
        On initialization of the form, crispy forms renders this layout
        """

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

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

        # The main "layout" defined
        self.helper.layout = Layout(
            Fieldset('',
            #######################
                Accordion(
                    # Step 1: Long URL
                    AccordionGroup('Step 1: Long URL',
                        Div(
                            HTML("""
                                <h4>Modify the URL you would like to shorten:</h4>
                                <br />"""),
                            'target',
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),

                    # Step 2: Short URL
                    AccordionGroup('Step 2: Short URL',
                        Div(
                            HTML("""
                                <h4>Modify the Go address:</h4>
                                <br />"""),
                            PrependedText(
                            'short', 'https://go.gmu.edu/', template='crispy/customPrepended.html'),
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html',),

                    # Step 3: Expiration
                    AccordionGroup('Step 3: URL Expiration',
                        Div(
                            HTML("""
                                <h4>Modify the expiration date:</h4>
                                <br />"""),
                            'expires',
                            Field('expires_custom', template="crispy/customDateField.html"),
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),

                # FIN
                template='crispy/accordian.html'),
            #######################
            HTML("""
                <br />"""),
            StrictButton('Submit Changes', css_class="btn btn-primary btn-md col-md-4", type='submit')))
    
    class Meta(URLForm.Meta):
        # what attributes are included
        fields = URLForm.Meta.fields

308
class SignupForm(ModelForm):
309
    """
David Haynes's avatar
David Haynes committed
310
    The form that is used when a user is signing up to be a RegisteredUser
311
    """
312

David Haynes's avatar
David Haynes committed
313
    # The full name of the RegisteredUser
314
    full_name = CharField(
315
316
317
        required=True,
        label='Full Name (Required)',
        max_length=100,
318
        widget=TextInput(),
David Haynes's avatar
David Haynes committed
319
        help_text="We can fill in this field based on information provided by https://peoplefinder.gmu.edu.",
320
    )
David Haynes's avatar
David Haynes committed
321
322

    # The RegisteredUser's chosen organization
323
    organization = CharField(
324
325
326
        required=True,
        label='Organization (Required)',
        max_length=100,
327
        widget=TextInput(),
David Haynes's avatar
David Haynes committed
328
        help_text="Or whatever \"group\" you would associate with on campus.",
329
    )
David Haynes's avatar
David Haynes committed
330
331

    # The RegisteredUser's reason for signing up to us Go
332
    description = CharField(
333
334
335
        required=False,
        label='Description (Optional)',
        max_length=200,
336
        widget=Textarea(),
David Haynes's avatar
David Haynes committed
337
        help_text="Describe what type of links you would intend to create with Go.",
338
    )
David Haynes's avatar
David Haynes committed
339

340
    # A user becomes registered when they agree to the TOS
341
    registered = BooleanField(
342
        required=True,
David Haynes's avatar
David Haynes committed
343
344
        # ***Need to replace lower url with production URL***
        # ie. go.gmu.edu/about#terms
345
346
347
        label=mark_safe(
            'Do you accept the <a href="http://127.0.0.1:8000/about#terms">Terms of Service</a>?'
        ),
David Haynes's avatar
David Haynes committed
348
        help_text="Esssentially the GMU Responsible Use of Computing policies.",
349
350
    )

351
    def __init__(self, request, *args, **kwargs):
David Haynes's avatar
David Haynes committed
352
353
354
355
356
357
        """
        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
358
359
        self.request = request
        super(SignupForm, self).__init__(*args, **kwargs)
David Haynes's avatar
David Haynes committed
360
        self.helper = FormHelper()
361
362
363
364
365
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-4'
        self.helper.field_class = 'col-md-6'

        self.helper.layout = Layout(
366
            Fieldset('',
367
                Div(
368
                    # Place in form fields
369
370
371
372
                    Div(
                        'full_name',
                        'organization',
                        'description',
373
                        'registered',
374
375
376
                        css_class='well'),

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

380
    class Meta:
David Haynes's avatar
David Haynes committed
381
382
383
384
        """
        Metadata about this ModelForm
        """

David Haynes's avatar
David Haynes committed
385
        # what model this form is for
386
        model = RegisteredUser
David Haynes's avatar
David Haynes committed
387
        # what attributes are included
388
        fields = ['full_name', 'organization', 'description', 'registered']