forms.py 9.79 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 import forms
15
from django.core.exceptions import ValidationError
Matthew Rodgers's avatar
Matthew Rodgers committed
16
from django.utils import timezone
David Haynes's avatar
David Haynes committed
17
from django.utils.safestring import mark_safe
18 19 20 21 22

# App Imports
from go.models import URL, RegisteredUser

# Other Imports
23
from bootstrap3_datetime.widgets import DateTimePicker
David Haynes's avatar
David Haynes committed
24 25 26 27 28
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

29

30
class URLForm(forms.ModelForm):
31
    """
David Haynes's avatar
David Haynes committed
32
    The form that is used in URL creation.
33
    """
34

David Haynes's avatar
David Haynes committed
35 36
    # target -------------------------------------------------------------------

37
    def clean_target(self):
David Haynes's avatar
David Haynes committed
38 39 40 41
        """
        Prevent redirect loop links
        """

David Haynes's avatar
David Haynes committed
42
        # get the entered target link
43
        target = self.cleaned_data.get('target')
David Haynes's avatar
David Haynes committed
44

45 46
        try:
            final_url = urllib.request.urlopen(target).geturl()
47 48
        # if visiting the provided url results in an HTTP error, or redirects
        # to a page that results in an HTTP error
49 50 51
        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
52
            if any(int(str(e)[11:14]) == errorNum for errorNum in range(300, 308)):
53
                raise ValidationError("Link results in a 300 error")
54 55
            else:
                final_url = ""
David Haynes's avatar
David Haynes committed
56 57 58 59 60 61 62 63 64 65 66

        # 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
67

68 69 70
    # Custom target URL field
    target = forms.URLField(
        required=True,
71
        label='Long URL (Required)',
72 73
        max_length=1000,
        widget=forms.URLInput(attrs={
74
            'placeholder': 'https://yoursite.com/'
75 76 77
        })
    )

David Haynes's avatar
David Haynes committed
78 79
    # short --------------------------------------------------------------------

80
    def unique_short(value):
David Haynes's avatar
David Haynes committed
81 82 83 84
        """
        Check to make sure the short url has not been used
        """

85
        try:
David Haynes's avatar
David Haynes committed
86
            # if we're able to get a URL with the same short url
87
            URL.objects.get(short__iexact=value)
88
        except URL.DoesNotExist as ex:
89
            return
David Haynes's avatar
David Haynes committed
90

David Haynes's avatar
David Haynes committed
91
        # then raise a ValidationError
92 93 94 95
        raise ValidationError('Short url already exists.')

    # Custom short-url field with validators.
    short = forms.SlugField(
96 97 98 99 100 101
        required=False,
        label='Short URL (Optional)',
        widget=forms.TextInput(),
        validators=[unique_short],
        max_length=20,
        min_length=3,
102 103
    )

David Haynes's avatar
David Haynes committed
104 105 106
    # expires ------------------------------------------------------------------

    # Define some string date standards
107 108 109
    DAY = '1 Day'
    WEEK = '1 Week'
    MONTH = '1 Month'
110
    CUSTOM = 'Custom Date'
111 112
    NEVER = 'Never'

David Haynes's avatar
David Haynes committed
113
    # Define a tuple of string date standards to be used as our date choices
114 115 116 117 118
    EXPIRATION_CHOICES = (
        (DAY, DAY),
        (WEEK, WEEK),
        (MONTH, MONTH),
        (NEVER, NEVER),
119
        (CUSTOM, CUSTOM),
120 121
    )

Matthew Rodgers's avatar
Matthew Rodgers committed
122
    # Add preset expiration choices.
123
    expires = forms.ChoiceField(
124 125 126 127 128
        required=True,
        label='Expiration (Required)',
        choices=EXPIRATION_CHOICES,
        initial=NEVER,
        widget=forms.RadioSelect(),
129 130
    )

Matthew Rodgers's avatar
Matthew Rodgers committed
131
    def valid_date(value):
David Haynes's avatar
David Haynes committed
132 133 134 135
        """
        Check if the selected date is a valid date
        """

David Haynes's avatar
David Haynes committed
136
        # a valid date is one that is greater than today
Matthew Rodgers's avatar
Matthew Rodgers committed
137 138
        if value > timezone.now():
            return
David Haynes's avatar
David Haynes committed
139
        # raise a ValidationError if the date is invalid
Matthew Rodgers's avatar
Matthew Rodgers committed
140
        else:
141
            raise ValidationError('Date must be after today.')
Matthew Rodgers's avatar
Matthew Rodgers committed
142 143


Matthew Rodgers's avatar
Matthew Rodgers committed
144
    # Add a custom expiration choice.
145
    expires_custom = forms.DateTimeField(
146 147 148 149 150 151
        required=False,
        label='Custom Date',
        input_formats=['%m-%d-%Y'],
        validators=[valid_date],
        initial=lambda: datetime.now() + timedelta(days=1),
        widget=DateTimePicker(
152 153 154 155 156 157
            options={
                "format": "MM-DD-YYYY",
                "pickTime": False,
            },
            icon_attrs={
                "class": "fa fa-calendar",
David Haynes's avatar
David Haynes committed
158 159
            },
        )
160 161
    )

162
    def __init__(self, *args, **kwargs):
David Haynes's avatar
David Haynes committed
163 164 165 166
        """
        On initialization of the form, crispy forms renders this layout
        """

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

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

179
        # The main "layout" defined
180
        self.helper.layout = Layout(
181 182
            Fieldset('',
            #######################
183
                Accordion(
184
                    # Step 1: Long URL
185 186 187 188
                    AccordionGroup('Step 1: Long URL',
                        Div(
                            HTML("""
                                <h4>Paste the URL you would like to shorten:</h4>
189
                                <br />"""),
190
                            'target',
191 192 193
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),
194 195

                    # Step 2: Short URL
196 197 198 199
                    AccordionGroup('Step 2: Short URL',
                        Div(
                            HTML("""
                                <h4>Create a custom Go address:</h4>
200 201
                                <br />"""),
                            PrependedText(
202
                            'short', 'https://go.gmu.edu/', template='crispy/customPrepended.html'),
203 204 205
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html',),
206 207

                    # Step 3: Expiration
208 209 210 211
                    AccordionGroup('Step 3: URL Expiration',
                        Div(
                            HTML("""
                                <h4>Set when you would like your Go address to expire:</h4>
212
                                <br />"""),
213
                            'expires',
214
                            Field('expires_custom', template="crispy/customDateField.html"),
215 216 217
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),
218

219 220
                # FIN
                template='crispy/accordian.html'),
221 222 223 224
            #######################
            HTML("""
                <br />"""),
            StrictButton('Shorten', css_class="btn btn-primary btn-md col-md-4", type='submit')))
225

226
    class Meta:
David Haynes's avatar
David Haynes committed
227 228 229 230
        """
        Metadata about this ModelForm
        """

David Haynes's avatar
David Haynes committed
231
        # what model this form is for
232
        model = URL
David Haynes's avatar
David Haynes committed
233
        # what attributes are included
234
        fields = ['target']
235

236
class SignupForm(forms.ModelForm):
237
    """
David Haynes's avatar
David Haynes committed
238
    The form that is used when a user is signing up to be a RegisteredUser
239
    """
240

David Haynes's avatar
David Haynes committed
241
    # The full name of the RegisteredUser
242
    full_name = forms.CharField(
243 244 245 246
        required=True,
        label='Full Name (Required)',
        max_length=100,
        widget=forms.TextInput(),
247
    )
David Haynes's avatar
David Haynes committed
248 249

    # The RegisteredUser's chosen organization
250
    organization = forms.CharField(
251 252 253 254
        required=True,
        label='Organization (Required)',
        max_length=100,
        widget=forms.TextInput(),
255
    )
David Haynes's avatar
David Haynes committed
256 257

    # The RegisteredUser's reason for signing up to us Go
258
    description = forms.CharField(
259 260 261 262
        required=False,
        label='Description (Optional)',
        max_length=200,
        widget=forms.Textarea(),
263
    )
David Haynes's avatar
David Haynes committed
264

265 266
    # A user becomes registered when they agree to the TOS
    registered = forms.BooleanField(
267
        required=True,
David Haynes's avatar
David Haynes committed
268 269
        # ***Need to replace lower url with production URL***
        # ie. go.gmu.edu/about#terms
270 271 272
        label=mark_safe(
            'Do you accept the <a href="http://127.0.0.1:8000/about#terms">Terms of Service</a>?'
        ),
273 274
    )

275
    def __init__(self, request, *args, **kwargs):
David Haynes's avatar
David Haynes committed
276 277 278 279 280 281
        """
        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
282 283
        self.request = request
        super(SignupForm, self).__init__(*args, **kwargs)
David Haynes's avatar
David Haynes committed
284
        self.helper = FormHelper()
285 286 287 288 289
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-4'
        self.helper.field_class = 'col-md-6'

        self.helper.layout = Layout(
290
            Fieldset('',
291
                Div(
292
                    # Place in form fields
293 294 295 296
                    Div(
                        'full_name',
                        'organization',
                        'description',
297
                        'registered',
298 299 300
                        css_class='well'),

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

304
    class Meta:
David Haynes's avatar
David Haynes committed
305 306 307 308
        """
        Metadata about this ModelForm
        """

David Haynes's avatar
David Haynes committed
309
        # what model this form is for
310
        model = RegisteredUser
David Haynes's avatar
David Haynes committed
311
        # what attributes are included
312
        fields = ['full_name', 'organization', 'description', 'registered']