forms.py 12.3 KB
Newer Older
1 2
"""
go/forms.py
3

David Haynes's avatar
David Haynes committed
4 5
Configure the layout and styling of the Go's forms.
"""
6
# Python stdlib Imports
David Haynes's avatar
David Haynes committed
7
from datetime import datetime, timedelta
8

9
# Django Imports
10
from django.core.exceptions import ValidationError
11 12 13
from django.forms import (BooleanField, CharField, ChoiceField, DateTimeField,
                          ModelForm, RadioSelect, SlugField, Textarea,
                          TextInput, URLField, URLInput)
Matthew Rodgers's avatar
Matthew Rodgers committed
14
from django.utils import timezone
David Haynes's avatar
David Haynes committed
15
from django.utils.safestring import mark_safe
16 17

# App Imports
18
from .models import URL, RegisteredUser
19 20

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

26

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

David Haynes's avatar
David Haynes committed
31 32
    Define custom fields and then render them onto the template.
    """
David Haynes's avatar
David Haynes committed
33 34
    # destination ------------------------------------------------------------------
    destination = URLField(
35
        required=True,
36
        label='Long URL (Required)',
37
        max_length=1000,
38
        widget=URLInput(attrs={
39
            'placeholder': 'https://yoursite.com/'
40 41 42
        })
    )

David Haynes's avatar
David Haynes committed
43
    # short -------------------------------------------------------------------
44
    def unique_short(value):
David Haynes's avatar
David Haynes committed
45 46 47
        """
        Check to make sure the short url has not been used
        """
48
        try:
David Haynes's avatar
David Haynes committed
49
            # if we're able to get a URL with the same short url
50
            URL.objects.get(short__iexact=value)
51
        except URL.DoesNotExist as ex:
David Haynes's avatar
David Haynes committed
52
            print(ex)
53
            return
David Haynes's avatar
David Haynes committed
54

David Haynes's avatar
David Haynes committed
55
        # then raise a ValidationError
56 57
        raise ValidationError('Short url already exists.')

David Haynes's avatar
David Haynes committed
58
    short = CharField(
59 60
        required=False,
        label='Short URL (Optional)',
61
        widget=TextInput(),
62 63 64
        validators=[unique_short],
        max_length=20,
        min_length=3,
65 66
    )

David Haynes's avatar
David Haynes committed
67
    # expires -----------------------------------------------------------------
68 69 70
    DAY = '1 Day'
    WEEK = '1 Week'
    MONTH = '1 Month'
71
    CUSTOM = 'Custom Date'
72 73
    NEVER = 'Never'

David Haynes's avatar
David Haynes committed
74
    # Define a tuple of string date standards to be used as our date choices
75 76 77 78 79
    EXPIRATION_CHOICES = (
        (DAY, DAY),
        (WEEK, WEEK),
        (MONTH, MONTH),
        (NEVER, NEVER),
80
        (CUSTOM, CUSTOM),
81 82
    )

83
    expires = ChoiceField(
84 85 86 87
        required=True,
        label='Expiration (Required)',
        choices=EXPIRATION_CHOICES,
        initial=NEVER,
88
        widget=RadioSelect(),
89 90
    )

Matthew Rodgers's avatar
Matthew Rodgers committed
91
    def valid_date(value):
David Haynes's avatar
David Haynes committed
92 93 94
        """
        Check if the selected date is a valid date
        """
David Haynes's avatar
David Haynes committed
95
        # a valid date is one that is greater than today
Matthew Rodgers's avatar
Matthew Rodgers committed
96 97
        if value > timezone.now():
            return
David Haynes's avatar
David Haynes committed
98
        # raise a ValidationError if the date is invalid
Matthew Rodgers's avatar
Matthew Rodgers committed
99
        else:
100
            raise ValidationError('Date must be after today.')
Matthew Rodgers's avatar
Matthew Rodgers committed
101

102
    expires_custom = DateTimeField(
103 104 105 106
        required=False,
        label='Custom Date',
        input_formats=['%m-%d-%Y'],
        validators=[valid_date],
David Haynes's avatar
David Haynes committed
107
        initial=lambda: datetime.now() + timedelta(days=1)
108 109
    )

110
    def __init__(self, *args, **kwargs):
David Haynes's avatar
David Haynes committed
111
        """
David Haynes's avatar
David Haynes committed
112
        On initialization of the form, crispy forms renders this layout.
David Haynes's avatar
David Haynes committed
113
        """
114 115
        # Grab that host info
        self.host = kwargs.pop('host', None)
116
        super(URLForm, self).__init__(*args, **kwargs)
117
        # Define the basics for crispy-forms
118
        self.helper = FormHelper()
119
        self.helper.form_method = 'POST'
120

David Haynes's avatar
David Haynes committed
121
        # Some extra vars for form css purposes
122 123 124 125
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-1'
        self.helper.field_class = 'col-md-6'

126
        # The main "layout" defined
127
        self.helper.layout = Layout(
128
            Fieldset('',
David Haynes's avatar
David Haynes committed
129 130 131 132 133 134
                     #######################
                     Accordion(
                         # Step 1: Long URL
                         AccordionGroup('Step 1: Long URL',
                                        Div(
                                            HTML("""
135
                                <h4>Paste the URL you would like to shorten:</h4>
136
                                <br />"""),
David Haynes's avatar
David Haynes committed
137
                                            'destination',
David Haynes's avatar
David Haynes committed
138 139 140 141 142 143 144 145
                                            style="background: rgb(#F6F6F6);"),
                                        active=True,
                                        template='crispy/accordian-group.html'),

                         # Step 2: Short URL
                         AccordionGroup('Step 2: Short URL',
                                        Div(
                                            HTML("""
146
                                <h4>Create a custom Go address:</h4>
147
                                <br />"""),
David Haynes's avatar
David Haynes committed
148 149 150 151 152 153 154 155 156 157
                                            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("""
158
                                <h4>Set when you would like your Go address to expire:</h4>
159
                                <br />"""),
David Haynes's avatar
David Haynes committed
160 161 162 163 164 165 166 167 168 169
                                            'expires',
                                            Field('expires_custom'),
                                            style="background: rgb(#F6F6F6);"),
                                        active=True,
                                        template='crispy/accordian-group.html'),

                         # FIN
                         template='crispy/accordian.html'),
                     #######################
                     HTML("""
170
                <br />"""),
David Haynes's avatar
David Haynes committed
171
                     StrictButton('Shorten', css_class="btn btn-primary btn-md col-md-4", type='submit')))
172

173
    class Meta:
David Haynes's avatar
David Haynes committed
174 175 176
        """
        Metadata about this ModelForm
        """
David Haynes's avatar
David Haynes committed
177
        # what model this form is for
178
        model = URL
David Haynes's avatar
David Haynes committed
179
        # what attributes are included
David Haynes's avatar
David Haynes committed
180
        fields = ['destination']
181

David Haynes's avatar
David Haynes committed
182

183
class EditForm(URLForm):
David Haynes's avatar
David Haynes committed
184 185
    """
    The form that is used in editing URLs.
186

David Haynes's avatar
David Haynes committed
187 188 189
    A modification of the URL creation form... now for editing URLs. Inherit
    custom form fields for DRY purposes.
    """
David Haynes's avatar
David Haynes committed
190

191 192
    def __init__(self, *args, **kwargs):
        """
David Haynes's avatar
David Haynes committed
193
        On initialization of the form, crispy forms renders this layout.
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        """
        # 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('',
David Haynes's avatar
David Haynes committed
210 211 212 213 214 215
                     #######################
                     Accordion(
                         # Step 1: Long URL
                         AccordionGroup('Step 1: Long URL',
                                        Div(
                                            HTML("""
216 217
                                <h4>Modify the URL you would like to shorten:</h4>
                                <br />"""),
David Haynes's avatar
David Haynes committed
218
                                            'destination',
David Haynes's avatar
David Haynes committed
219 220 221 222 223 224 225 226
                                            style="background: rgb(#F6F6F6);"),
                                        active=True,
                                        template='crispy/accordian-group.html'),

                         # Step 2: Short URL
                         AccordionGroup('Step 2: Short URL',
                                        Div(
                                            HTML("""
227 228
                                <h4>Modify the Go address:</h4>
                                <br />"""),
David Haynes's avatar
David Haynes committed
229 230 231 232 233 234 235 236 237 238
                                            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("""
239 240
                                <h4>Modify the expiration date:</h4>
                                <br />"""),
David Haynes's avatar
David Haynes committed
241 242 243 244 245 246 247 248 249 250 251
                                            '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("""
252
                <br />"""),
David Haynes's avatar
David Haynes committed
253
                     StrictButton('Submit Changes', css_class="btn btn-primary btn-md col-md-4", type='submit')))
David Haynes's avatar
David Haynes committed
254

255
    class Meta(URLForm.Meta):
David Haynes's avatar
David Haynes committed
256 257 258
        """
        Metadata about this ModelForm
        """
259 260 261
        # what attributes are included
        fields = URLForm.Meta.fields

David Haynes's avatar
David Haynes committed
262

263
class SignupForm(ModelForm):
264
    """
David Haynes's avatar
David Haynes committed
265
    The form that is used when a user is signing up to be a RegisteredUser
266
    """
267
    full_name = CharField(
268 269 270
        required=True,
        label='Full Name (Required)',
        max_length=100,
271
        widget=TextInput(),
David Haynes's avatar
David Haynes committed
272
        help_text="We can fill in this field based on information provided by https://peoplefinder.gmu.edu.",
273
    )
David Haynes's avatar
David Haynes committed
274

275
    organization = CharField(
276 277 278
        required=True,
        label='Organization (Required)',
        max_length=100,
279
        widget=TextInput(),
David Haynes's avatar
David Haynes committed
280
        help_text="Or whatever \"group\" you would associate with on campus.",
281
    )
David Haynes's avatar
David Haynes committed
282

283
    description = CharField(
284 285 286
        required=False,
        label='Description (Optional)',
        max_length=200,
287
        widget=Textarea(),
David Haynes's avatar
David Haynes committed
288
        help_text="Describe what type of links you would intend to create with Go.",
289
    )
David Haynes's avatar
David Haynes committed
290

291
    # A user becomes registered when they agree to the TOS
292
    registered = BooleanField(
293
        required=True,
David Haynes's avatar
David Haynes committed
294 295
        # ***Need to replace lower url with production URL***
        # ie. go.gmu.edu/about#terms
296
        label=mark_safe(
David Haynes's avatar
David Haynes committed
297
            'Do you accept the <a href="about">Terms of Service</a>?'
298
        ),
David Haynes's avatar
David Haynes committed
299
        help_text="Esssentially the GMU Responsible Use of Computing policies.",
300 301
    )

302
    def __init__(self, request, *args, **kwargs):
David Haynes's avatar
David Haynes committed
303
        """
David Haynes's avatar
David Haynes committed
304
        On initialization of the form, crispy forms renders this layout.
David Haynes's avatar
David Haynes committed
305 306 307
        """
        # Necessary to call request in forms.py, is otherwise restricted to
        # views.py and models.py
308 309
        self.request = request
        super(SignupForm, self).__init__(*args, **kwargs)
David Haynes's avatar
David Haynes committed
310
        self.helper = FormHelper()
311 312 313 314 315
        self.helper.form_class = 'form-horizontal'
        self.helper.label_class = 'col-md-4'
        self.helper.field_class = 'col-md-6'

        self.helper.layout = Layout(
316
            Fieldset('',
David Haynes's avatar
David Haynes committed
317 318 319 320 321 322 323 324 325 326 327 328 329
                     Div(
                         # Place in form fields
                         Div(
                             'full_name',
                             'organization',
                             'description',
                             'registered',
                             css_class='well'),

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

331
    class Meta:
David Haynes's avatar
David Haynes committed
332 333 334
        """
        Metadata about this ModelForm
        """
David Haynes's avatar
David Haynes committed
335
        # what model this form is for
336
        model = RegisteredUser
David Haynes's avatar
David Haynes committed
337
        # what attributes are included
338
        fields = ['full_name', 'organization', 'description', 'registered']