forms.py 9.09 KB
Newer Older
1 2 3
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division

4 5 6 7
# Python stdlib Imports
from datetime import date, datetime, timedelta
from six.moves import urllib

8
# Django Imports
9
from django import forms
10
from django.core.exceptions import ValidationError
11
from django.utils.safestring import mark_safe
Matthew Rodgers's avatar
Matthew Rodgers committed
12
from django.utils import timezone
13 14 15 16 17

# App Imports
from go.models import URL, RegisteredUser

# Other Imports
David Haynes's avatar
David Haynes committed
18
from crispy_forms.helper import FormHelper
19 20
from crispy_forms.layout import Layout, Fieldset, Submit, HTML, Div, Field
from crispy_forms.bootstrap import StrictButton, PrependedText, Accordion, AccordionGroup
21
from bootstrap3_datetime.widgets import DateTimePicker
22

23
class URLForm(forms.ModelForm):
24 25 26
    """
        The form that is used in URL creation.
    """
27

28 29
    # Prevent redirect loop links
    def clean_target(self):
David Haynes's avatar
David Haynes committed
30
        # get the entered target link
31
        target = self.cleaned_data.get('target')
32 33
        try:
            final_url = urllib.request.urlopen(target).geturl()
34 35
        # if visiting the provided url results in an HTTP error, or redirects
        # to a page that results in an HTTP error
36 37 38
        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
39
            if any(int(str(e)[11:14]) == errorNum for errorNum in range(300, 308)):
40
                raise ValidationError("Link results in a 300 error")
41 42
            else:
                final_url = ""
43 44
        # if the host (go.gmu.edu) is in the entered target link or where it
        # redirects
45
        if self.host in final_url or self.host in target:
46 47 48 49
            raise ValidationError("You can't make a Go link to Go silly!")
        else:
            return target

50 51 52
    # Custom target URL field
    target = forms.URLField(
        required=True,
53
        label='Long URL (Required)',
54 55
        max_length=1000,
        widget=forms.URLInput(attrs={
56
            'placeholder': 'https://yoursite.com/'
57 58 59 60 61 62
        })
    )

    # Check to make sure the short url has not been used
    def unique_short(value):
        try:
David Haynes's avatar
David Haynes committed
63
            # if we're able to get a URL with the same short url
64
            URL.objects.get(short__iexact=value)
65
        except URL.DoesNotExist as ex:
66
            return
David Haynes's avatar
David Haynes committed
67
        # then raise a ValidationError
68 69 70 71
        raise ValidationError('Short url already exists.')

    # Custom short-url field with validators.
    short = forms.SlugField(
72 73 74 75 76 77
        required=False,
        label='Short URL (Optional)',
        widget=forms.TextInput(),
        validators=[unique_short],
        max_length=20,
        min_length=3,
78 79
    )

David Haynes's avatar
David Haynes committed
80
    # define some string date standards
81 82 83
    DAY = '1 Day'
    WEEK = '1 Week'
    MONTH = '1 Month'
84
    CUSTOM = 'Custom Date'
85 86
    NEVER = 'Never'

David Haynes's avatar
David Haynes committed
87
    # define a tuple of string date standards to be used as our date choices
88 89 90 91 92
    EXPIRATION_CHOICES = (
        (DAY, DAY),
        (WEEK, WEEK),
        (MONTH, MONTH),
        (NEVER, NEVER),
93
        (CUSTOM, CUSTOM),
94 95
    )

Matthew Rodgers's avatar
Matthew Rodgers committed
96
    # Add preset expiration choices.
97
    expires = forms.ChoiceField(
98 99 100 101 102
        required=True,
        label='Expiration (Required)',
        choices=EXPIRATION_CHOICES,
        initial=NEVER,
        widget=forms.RadioSelect(),
103 104
    )

David Haynes's avatar
David Haynes committed
105
    # Check if the selected date is a valid date
Matthew Rodgers's avatar
Matthew Rodgers committed
106
    def valid_date(value):
David Haynes's avatar
David Haynes committed
107
        # a valid date is one that is greater than today
Matthew Rodgers's avatar
Matthew Rodgers committed
108 109
        if value > timezone.now():
            return
David Haynes's avatar
David Haynes committed
110
        # raise a ValidationError if the date is invalid
Matthew Rodgers's avatar
Matthew Rodgers committed
111
        else:
112
            raise ValidationError('Date must be after today.')
Matthew Rodgers's avatar
Matthew Rodgers committed
113 114


Matthew Rodgers's avatar
Matthew Rodgers committed
115
    # Add a custom expiration choice.
116
    expires_custom = forms.DateTimeField(
117 118 119 120 121 122
        required=False,
        label='Custom Date',
        input_formats=['%m-%d-%Y'],
        validators=[valid_date],
        initial=lambda: datetime.now() + timedelta(days=1),
        widget=DateTimePicker(
123 124 125 126 127 128
            options={
                "format": "MM-DD-YYYY",
                "pickTime": False,
            },
            icon_attrs={
                "class": "fa fa-calendar",
David Haynes's avatar
David Haynes committed
129 130
            },
        )
131 132
    )

David Haynes's avatar
David Haynes committed
133
    # on initialization of the form, crispy forms renders this layout
134
    def __init__(self, *args, **kwargs):
135 136
        # Grab that host info
        self.host = kwargs.pop('host', None)
137
        super(URLForm, self).__init__(*args, **kwargs)
138
        # Define the basics for crispy-forms
139
        self.helper = FormHelper()
140
        self.helper.form_method = 'POST'
141

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

147
        # The main "layout" defined
148
        self.helper.layout = Layout(
149 150
            Fieldset('',
            #######################
151
                Accordion(
152
                    # Step 1: Long URL
153 154 155 156
                    AccordionGroup('Step 1: Long URL',
                        Div(
                            HTML("""
                                <h4>Paste the URL you would like to shorten:</h4>
157
                                <br />"""),
158
                            'target',
159 160 161
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),
162 163

                    # Step 2: Short URL
164 165 166 167
                    AccordionGroup('Step 2: Short URL',
                        Div(
                            HTML("""
                                <h4>Create a custom Go address:</h4>
168 169
                                <br />"""),
                            PrependedText(
170
                            'short', 'https://go.gmu.edu/', template='crispy/customPrepended.html'),
171 172 173
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html',),
174 175

                    # Step 3: Expiration
176 177 178 179
                    AccordionGroup('Step 3: URL Expiration',
                        Div(
                            HTML("""
                                <h4>Set when you would like your Go address to expire:</h4>
180
                                <br />"""),
181
                            'expires',
182
                            Field('expires_custom', template="crispy/customDateField.html"),
183 184 185
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),
186

187 188
                # FIN
                template='crispy/accordian.html'),
189 190 191 192
            #######################
            HTML("""
                <br />"""),
            StrictButton('Shorten', css_class="btn btn-primary btn-md col-md-4", type='submit')))
193

David Haynes's avatar
David Haynes committed
194
    # metadata about this ModelForm
195
    class Meta:
David Haynes's avatar
David Haynes committed
196
        # what model this form is for
197
        model = URL
David Haynes's avatar
David Haynes committed
198
        # what attributes are included
199
        fields = ['target']
200

201
class SignupForm(forms.ModelForm):
202 203 204
    """
        The form that is used when a user is signing up to be a RegisteredUser
    """
205

David Haynes's avatar
David Haynes committed
206
    # The full name of the RegisteredUser
207
    full_name = forms.CharField(
208 209 210 211
        required=True,
        label='Full Name (Required)',
        max_length=100,
        widget=forms.TextInput(),
212
    )
David Haynes's avatar
David Haynes committed
213 214

    # The RegisteredUser's chosen organization
215
    organization = forms.CharField(
216 217 218 219
        required=True,
        label='Organization (Required)',
        max_length=100,
        widget=forms.TextInput(),
220
    )
David Haynes's avatar
David Haynes committed
221 222

    # The RegisteredUser's reason for signing up to us Go
223
    description = forms.CharField(
224 225 226 227
        required=False,
        label='Description (Optional)',
        max_length=200,
        widget=forms.Textarea(),
228
    )
David Haynes's avatar
David Haynes committed
229

230 231
    # A user becomes registered when they agree to the TOS
    registered = forms.BooleanField(
232
        required=True,
233
        # ***Need to replace lower url with production URL*** ie. go.gmu.edu/about#terms
234 235 236
        label=mark_safe(
            'Do you accept the <a href="http://127.0.0.1:8000/about#terms">Terms of Service</a>?'
        ),
237 238
    )

David Haynes's avatar
David Haynes committed
239
    # on initialization of the form, crispy forms renders this layout
240 241 242 243
    def __init__(self, request, *args, **kwargs):
        # Necessary to call request in forms.py, is otherwise restricted to views.py and models.py
        self.request = request
        super(SignupForm, self).__init__(*args, **kwargs)
David Haynes's avatar
David Haynes committed
244
        self.helper = FormHelper()
245 246 247 248 249
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-4'
        self.helper.field_class = 'col-md-6'

        self.helper.layout = Layout(
250
            Fieldset('',
251
                Div(
252
                    # Place in form fields
253 254 255 256
                    Div(
                        'full_name',
                        'organization',
                        'description',
257
                        'registered',
258 259 260
                        css_class='well'),

                    # Extras at bottom
261
                    StrictButton('Submit',css_class='btn btn-primary btn-md col-md-4', type='submit'),
262
                    css_class='col-md-6')))
David Haynes's avatar
David Haynes committed
263 264

    # metadata about this ModelForm
265
    class Meta:
David Haynes's avatar
David Haynes committed
266
        # what model this form is for
267
        model = RegisteredUser
David Haynes's avatar
David Haynes committed
268
        # what attributes are included
269
        fields = ['full_name', 'organization', 'description', 'registered']