forms.py 12.3 KB
Newer Older
1 2 3
"""
go/forms.py
"""
4
# Future Imports
David Haynes's avatar
David Haynes committed
5 6
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
7

8
# Python stdlib Imports
David Haynes's avatar
David Haynes committed
9
from datetime import datetime, timedelta
10

11
# Django Imports
12
from django.core.exceptions import ValidationError
13 14 15
from django.forms import (BooleanField, CharField, ChoiceField, DateTimeField,
                          ModelForm, RadioSelect, SlugField, Textarea,
                          TextInput, URLField, URLInput)
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

# App Imports
20
from .models import URL, RegisteredUser
21 22

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

29

30
class URLForm(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
        """
        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 46 47 48 49 50 51 52 53

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

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

David Haynes's avatar
David Haynes committed
65 66
    # short --------------------------------------------------------------------

67
    def unique_short(value):
David Haynes's avatar
David Haynes committed
68 69 70 71
        """
        Check to make sure the short url has not been used
        """

72
        try:
David Haynes's avatar
David Haynes committed
73
            # if we're able to get a URL with the same short url
74
            URL.objects.get(short__iexact=value)
75
        except URL.DoesNotExist as ex:
76
            return
David Haynes's avatar
David Haynes committed
77

David Haynes's avatar
David Haynes committed
78
        # then raise a ValidationError
79 80 81
        raise ValidationError('Short url already exists.')

    # Custom short-url field with validators.
82
    short = SlugField(
83 84
        required=False,
        label='Short URL (Optional)',
85
        widget=TextInput(),
86 87 88
        validators=[unique_short],
        max_length=20,
        min_length=3,
89 90
    )

David Haynes's avatar
David Haynes committed
91 92 93
    # expires ------------------------------------------------------------------

    # Define some string date standards
94 95 96
    DAY = '1 Day'
    WEEK = '1 Week'
    MONTH = '1 Month'
97
    CUSTOM = 'Custom Date'
98 99
    NEVER = 'Never'

David Haynes's avatar
David Haynes committed
100
    # Define a tuple of string date standards to be used as our date choices
101 102 103 104 105
    EXPIRATION_CHOICES = (
        (DAY, DAY),
        (WEEK, WEEK),
        (MONTH, MONTH),
        (NEVER, NEVER),
106
        (CUSTOM, CUSTOM),
107 108
    )

Matthew Rodgers's avatar
Matthew Rodgers committed
109
    # Add preset expiration choices.
110
    expires = ChoiceField(
111 112 113 114
        required=True,
        label='Expiration (Required)',
        choices=EXPIRATION_CHOICES,
        initial=NEVER,
115
        widget=RadioSelect(),
116 117
    )

Matthew Rodgers's avatar
Matthew Rodgers committed
118
    def valid_date(value):
David Haynes's avatar
David Haynes committed
119 120 121 122
        """
        Check if the selected date is a valid date
        """

David Haynes's avatar
David Haynes committed
123
        # a valid date is one that is greater than today
Matthew Rodgers's avatar
Matthew Rodgers committed
124 125
        if value > timezone.now():
            return
David Haynes's avatar
David Haynes committed
126
        # raise a ValidationError if the date is invalid
Matthew Rodgers's avatar
Matthew Rodgers committed
127
        else:
128
            raise ValidationError('Date must be after today.')
Matthew Rodgers's avatar
Matthew Rodgers committed
129 130


Matthew Rodgers's avatar
Matthew Rodgers committed
131
    # Add a custom expiration choice.
132
    expires_custom = DateTimeField(
133 134 135 136 137 138
        required=False,
        label='Custom Date',
        input_formats=['%m-%d-%Y'],
        validators=[valid_date],
        initial=lambda: datetime.now() + timedelta(days=1),
        widget=DateTimePicker(
139 140 141 142 143 144
            options={
                "format": "MM-DD-YYYY",
                "pickTime": False,
            },
            icon_attrs={
                "class": "fa fa-calendar",
David Haynes's avatar
David Haynes committed
145 146
            },
        )
147 148
    )

149
    def __init__(self, *args, **kwargs):
David Haynes's avatar
David Haynes committed
150 151 152 153
        """
        On initialization of the form, crispy forms renders this layout
        """

154 155
        # Grab that host info
        self.host = kwargs.pop('host', None)
156
        super(URLForm, self).__init__(*args, **kwargs)
157
        # Define the basics for crispy-forms
158
        self.helper = FormHelper()
159
        self.helper.form_method = 'POST'
160

161
        # Some xtra vars for form css purposes
162 163 164 165
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-1'
        self.helper.field_class = 'col-md-6'

166
        # The main "layout" defined
167
        self.helper.layout = Layout(
168 169
            Fieldset('',
            #######################
170
                Accordion(
171
                    # Step 1: Long URL
172 173 174 175
                    AccordionGroup('Step 1: Long URL',
                        Div(
                            HTML("""
                                <h4>Paste the URL you would like to shorten:</h4>
176
                                <br />"""),
177
                            'target',
178 179 180
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),
181 182

                    # Step 2: Short URL
183 184 185 186
                    AccordionGroup('Step 2: Short URL',
                        Div(
                            HTML("""
                                <h4>Create a custom Go address:</h4>
187 188
                                <br />"""),
                            PrependedText(
189
                            'short', 'https://go.gmu.edu/', template='crispy/customPrepended.html'),
190 191 192
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html',),
193 194

                    # Step 3: Expiration
195 196 197 198
                    AccordionGroup('Step 3: URL Expiration',
                        Div(
                            HTML("""
                                <h4>Set when you would like your Go address to expire:</h4>
199
                                <br />"""),
200
                            'expires',
201
                            Field('expires_custom', template="crispy/customDateField.html"),
202 203 204
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),
205

206 207
                # FIN
                template='crispy/accordian.html'),
208 209 210 211
            #######################
            HTML("""
                <br />"""),
            StrictButton('Shorten', css_class="btn btn-primary btn-md col-md-4", type='submit')))
212

213
    class Meta:
David Haynes's avatar
David Haynes committed
214 215 216 217
        """
        Metadata about this ModelForm
        """

David Haynes's avatar
David Haynes committed
218
        # what model this form is for
219
        model = URL
David Haynes's avatar
David Haynes committed
220
        # what attributes are included
221
        fields = ['target']
222

223 224 225 226 227 228 229 230 231 232 233 234 235 236 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
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

293
class SignupForm(ModelForm):
294
    """
David Haynes's avatar
David Haynes committed
295
    The form that is used when a user is signing up to be a RegisteredUser
296
    """
297

David Haynes's avatar
David Haynes committed
298
    # The full name of the RegisteredUser
299
    full_name = CharField(
300 301 302
        required=True,
        label='Full Name (Required)',
        max_length=100,
303
        widget=TextInput(),
David Haynes's avatar
David Haynes committed
304
        help_text="We can fill in this field based on information provided by https://peoplefinder.gmu.edu.",
305
    )
David Haynes's avatar
David Haynes committed
306 307

    # The RegisteredUser's chosen organization
308
    organization = CharField(
309 310 311
        required=True,
        label='Organization (Required)',
        max_length=100,
312
        widget=TextInput(),
David Haynes's avatar
David Haynes committed
313
        help_text="Or whatever \"group\" you would associate with on campus.",
314
    )
David Haynes's avatar
David Haynes committed
315 316

    # The RegisteredUser's reason for signing up to us Go
317
    description = CharField(
318 319 320
        required=False,
        label='Description (Optional)',
        max_length=200,
321
        widget=Textarea(),
David Haynes's avatar
David Haynes committed
322
        help_text="Describe what type of links you would intend to create with Go.",
323
    )
David Haynes's avatar
David Haynes committed
324

325
    # A user becomes registered when they agree to the TOS
326
    registered = BooleanField(
327
        required=True,
David Haynes's avatar
David Haynes committed
328 329
        # ***Need to replace lower url with production URL***
        # ie. go.gmu.edu/about#terms
330 331 332
        label=mark_safe(
            'Do you accept the <a href="http://127.0.0.1:8000/about#terms">Terms of Service</a>?'
        ),
David Haynes's avatar
David Haynes committed
333
        help_text="Esssentially the GMU Responsible Use of Computing policies.",
334 335
    )

336
    def __init__(self, request, *args, **kwargs):
David Haynes's avatar
David Haynes committed
337 338 339 340 341 342
        """
        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
343 344
        self.request = request
        super(SignupForm, self).__init__(*args, **kwargs)
David Haynes's avatar
David Haynes committed
345
        self.helper = FormHelper()
346 347 348 349 350
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-4'
        self.helper.field_class = 'col-md-6'

        self.helper.layout = Layout(
351
            Fieldset('',
352
                Div(
353
                    # Place in form fields
354 355 356 357
                    Div(
                        'full_name',
                        'organization',
                        'description',
358
                        'registered',
359 360 361
                        css_class='well'),

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

365
    class Meta:
David Haynes's avatar
David Haynes committed
366 367 368 369
        """
        Metadata about this ModelForm
        """

David Haynes's avatar
David Haynes committed
370
        # what model this form is for
371
        model = RegisteredUser
David Haynes's avatar
David Haynes committed
372
        # what attributes are included
373
        fields = ['full_name', 'organization', 'description', 'registered']