forms.py 12.6 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 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(),
319
    )
David Haynes's avatar
David Haynes committed
320 321

    # The RegisteredUser's chosen organization
322
    organization = CharField(
323 324 325
        required=True,
        label='Organization (Required)',
        max_length=100,
326
        widget=TextInput(),
327
    )
David Haynes's avatar
David Haynes committed
328 329

    # The RegisteredUser's reason for signing up to us Go
330
    description = CharField(
331 332 333
        required=False,
        label='Description (Optional)',
        max_length=200,
334
        widget=Textarea(),
335
    )
David Haynes's avatar
David Haynes committed
336

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

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

        self.helper.layout = Layout(
362
            Fieldset('',
363
                Div(
364
                    # Place in form fields
365 366 367 368
                    Div(
                        'full_name',
                        'organization',
                        'description',
369
                        'registered',
370 371 372
                        css_class='well'),

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

376
    class Meta:
David Haynes's avatar
David Haynes committed
377 378 379 380
        """
        Metadata about this ModelForm
        """

David Haynes's avatar
David Haynes committed
381
        # what model this form is for
382
        model = RegisteredUser
David Haynes's avatar
David Haynes committed
383
        # what attributes are included
384
        fields = ['full_name', 'organization', 'description', 'registered']