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

4
# Django Imports
5
from django import forms
6
from django.core.exceptions import ValidationError
7
from django.utils.safestring import mark_safe
Matthew Rodgers's avatar
Matthew Rodgers committed
8
from django.utils import timezone
9 10 11 12 13

# App Imports
from go.models import URL, RegisteredUser

# Other Imports
David Haynes's avatar
David Haynes committed
14
from crispy_forms.helper import FormHelper
15 16
from crispy_forms.layout import Layout, Fieldset, Submit, HTML, Div, Field
from crispy_forms.bootstrap import StrictButton, PrependedText, Accordion, AccordionGroup
17
from bootstrap3_datetime.widgets import DateTimePicker
18
from datetime import date, datetime, timedelta
19
from six.moves import urllib
20

David Haynes's avatar
David Haynes committed
21 22 23
"""
    The form that is used in URL creation.
"""
24
class URLForm(forms.ModelForm):
25

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

David Haynes's avatar
David Haynes committed
199 200 201
"""
    The form that is used when a user is signing up to be a RegisteredUser
"""
202
class SignupForm(forms.ModelForm):
203

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

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

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

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

David Haynes's avatar
David Haynes committed
235
    # on initialization of the form, crispy forms renders this layout
236 237 238 239
    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
240
        self.helper = FormHelper()
241 242 243 244 245
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-4'
        self.helper.field_class = 'col-md-6'

        self.helper.layout = Layout(
246
            Fieldset('',
247
                Div(
248
                    # Place in form fields
249 250 251 252
                    Div(
                        'full_name',
                        'organization',
                        'description',
253
                        'registered',
254 255 256
                        css_class='well'),

                    # Extras at bottom
257
                    StrictButton('Submit',css_class='btn btn-primary btn-md col-md-4', type='submit'),
258
                    css_class='col-md-6')))
David Haynes's avatar
David Haynes committed
259 260

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