forms.py 11.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
21
from bootstrap3_datetime.widgets import DateTimePicker
David Haynes's avatar
David Haynes committed
22 23 24
from crispy_forms.bootstrap import (Accordion, AccordionGroup, PrependedText,
                                    StrictButton)
from crispy_forms.helper import FormHelper
David Haynes's avatar
David Haynes committed
25
from crispy_forms.layout import HTML, Div, Field, Fieldset, Layout
David Haynes's avatar
David Haynes committed
26

27

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

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

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

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

59
    short = SlugField(
60 61
        required=False,
        label='Short URL (Optional)',
62
        widget=TextInput(),
63 64 65
        validators=[unique_short],
        max_length=20,
        min_length=3,
66 67
    )

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

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

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

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

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

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

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

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

                    # Step 2: Short URL
144 145 146 147
                    AccordionGroup('Step 2: Short URL',
                        Div(
                            HTML("""
                                <h4>Create a custom Go address:</h4>
148 149
                                <br />"""),
                            PrependedText(
150
                            'short', 'https://go.gmu.edu/', template='crispy/customPrepended.html'),
151 152 153
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html',),
154 155

                    # Step 3: Expiration
156 157 158 159
                    AccordionGroup('Step 3: URL Expiration',
                        Div(
                            HTML("""
                                <h4>Set when you would like your Go address to expire:</h4>
160
                                <br />"""),
161
                            'expires',
David Haynes's avatar
David Haynes committed
162
                            Field('expires_custom'),
163 164 165
                        style="background: rgb(#F6F6F6);"),
                    active=True,
                    template='crispy/accordian-group.html'),
166

167 168
                # FIN
                template='crispy/accordian.html'),
169 170 171 172
            #######################
            HTML("""
                <br />"""),
            StrictButton('Shorten', css_class="btn btn-primary btn-md col-md-4", type='submit')))
173

174
    class Meta:
David Haynes's avatar
David Haynes committed
175 176 177
        """
        Metadata about this ModelForm
        """
David Haynes's avatar
David Haynes committed
178
        # what model this form is for
179
        model = URL
David Haynes's avatar
David Haynes committed
180
        # what attributes are included
181
        fields = ['target']
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.
    """
190 191
    def __init__(self, *args, **kwargs):
        """
David Haynes's avatar
David Haynes committed
192
        On initialization of the form, crispy forms renders this layout.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 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
        """
        # 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')))
David Haynes's avatar
David Haynes committed
252

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

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

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

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

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

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

        self.helper.layout = Layout(
313
            Fieldset('',
314
                Div(
315
                    # Place in form fields
316 317 318 319
                    Div(
                        'full_name',
                        'organization',
                        'description',
320
                        'registered',
321 322 323
                        css_class='well'),

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

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