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
16
from django.utils.safestring import mark_safe
Matthew Rodgers's avatar
Matthew Rodgers committed
17
from django.utils import timezone
18 19 20 21 22

# App Imports
from go.models import URL, RegisteredUser

# Other Imports
David Haynes's avatar
David Haynes committed
23
from crispy_forms.helper import FormHelper
24
from crispy_forms.layout import Layout, Fieldset, Submit, HTML, Div, Field
David Haynes's avatar
David Haynes committed
25 26
from crispy_forms.bootstrap import (StrictButton, PrependedText, Accordion, 
                                    AccordionGroup)
27
from bootstrap3_datetime.widgets import DateTimePicker
28

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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