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']