File: forms.py

package info (click to toggle)
django-recurrence 1.12.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,148 kB
  • sloc: python: 2,530; javascript: 2,502; makefile: 159; sh: 6
file content (222 lines) | stat: -rw-r--r-- 8,345 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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
from django import forms, urls
from django.conf import settings
from django.views import i18n
from django.utils.translation import gettext_lazy as _
from django.contrib.staticfiles.storage import staticfiles_storage

import recurrence
from recurrence import exceptions


class RecurrenceWidget(forms.Textarea):

    def __init__(self, attrs=None, **kwargs):
        self.js_widget_options = kwargs
        defaults = {'class': 'recurrence-widget'}
        if attrs is not None:
            defaults.update(attrs)
        super().__init__(defaults)

    def get_media(self):
        extra = '' if settings.DEBUG else '.min'
        js = [
            'admin/js/vendor/jquery/jquery%s.js' % extra,
            'admin/js/jquery.init.js',
            staticfiles_storage.url('recurrence/js/recurrence.js'),
            staticfiles_storage.url('recurrence/js/recurrence-widget.js'),
            staticfiles_storage.url('recurrence/js/recurrence-widget.init.js'),
        ]
        i18n_media = find_recurrence_i18n_js_catalog()
        if i18n_media:
            js.insert(0, i18n_media)

        return forms.Media(
            js=js, css={
                'all': (
                    staticfiles_storage.url('recurrence/css/recurrence.css'),
                ),
            },
        )
    media = property(get_media)


class RecurrenceField(forms.CharField):
    """
    A Field that accepts the recurrence related parameters of rfc2445.

    Values are deserialized into `recurrence.base.Recurrence` objects.
    """
    widget = RecurrenceWidget
    default_error_messages = {
        'invalid_frequency': _(
            u'Invalid frequency.'),
        'max_rrules_exceeded': _(
            u'Max rules exceeded. The limit is %(limit)s'),
        'max_exrules_exceeded': _(
            u'Max exclusion rules exceeded. The limit is %(limit)s'),
        'max_rdates_exceeded': _(
            u'Max dates exceeded. The limit is %(limit)s'),
        'max_exdates_exceeded': _(
            u'Max exclusion dates exceeded. The limit is %(limit)s'),
        'recurrence_required': _(
            u'This field is required. Set either a recurrence rule or date.'),
    }

    def __init__(
        self,
        frequencies=None, accept_dtstart=True, accept_dtend=True,
        max_rrules=None, max_exrules=None, max_rdates=None, max_exdates=None,
        *args, **kwargs
    ):
        """
        Create a recurrence field.

        A `RecurrenceField` takes the same parameters as a `CharField`
        field with some additional paramaters.

        :Parameters:
            `frequencies` : sequence
                A sequence of the frequency constants specifying which
                frequencies are valid for input. By default all
                frequencies are valid.

            `accept_dtstart` : bool
                Whether to accept a dtstart value passed in the input.

            `accept_dtend` : bool
                Whether to accept a dtend value passed in the input.

            `max_rrules` : int
                The max number of rrules to accept in the input. A
                value of ``0`` means input of rrules is disabled.

            `max_exrules` : int
                The max number of exrules to accept in the input. A
                value of ``0`` means input of exrules is disabled.

            `max_rdates` : int
                The max number of rdates to accept in the input. A
                value of ``0`` means input of rdates is disabled.

            `max_exdates` : int
                The max number of exdates to accept in the input. A
                value of ``0`` means input of exdates is disabled.
        """
        self.accept_dtstart = accept_dtstart
        self.accept_dtend = accept_dtend
        self.max_rrules = max_rrules
        self.max_exrules = max_exrules
        self.max_rdates = max_rdates
        self.max_exdates = max_exdates
        if frequencies is not None:
            self.frequencies = frequencies
        else:
            self.frequencies = (
                recurrence.YEARLY, recurrence.MONTHLY,
                recurrence.WEEKLY, recurrence.DAILY,
                recurrence.HOURLY, recurrence.MINUTELY,
                recurrence.SECONDLY,
            )
        super().__init__(*args, **kwargs)

    def clean(self, value):
        """
        Validates that ``value`` deserialized into a
        `recurrence.base.Recurrence` object falls within the
        parameters specified to the `RecurrenceField` constructor.
        """
        try:
            recurrence_obj = recurrence.deserialize(value)
        except exceptions.DeserializationError as error:
            raise forms.ValidationError(error.args[0])
        except TypeError:
            return None
        if not self.accept_dtstart:
            recurrence_obj.dtstart = None
        if not self.accept_dtend:
            recurrence_obj.dtend = None

        if self.max_rrules is not None:
            if len(recurrence_obj.rrules) > self.max_rrules:
                raise forms.ValidationError(
                    self.error_messages['max_rrules_exceeded'] % {
                        'limit': self.max_rrules
                    }
                )
        if self.max_exrules is not None:
            if len(recurrence_obj.exrules) > self.max_exrules:
                raise forms.ValidationError(
                    self.error_messages['max_exrules_exceeded'] % {
                        'limit': self.max_exrules
                    }
                )
        if self.max_rdates is not None:
            if len(recurrence_obj.rdates) > self.max_rdates:
                raise forms.ValidationError(
                    self.error_messages['max_rdates_exceeded'] % {
                        'limit': self.max_rdates
                    }
                )
        if self.max_exdates is not None:
            if len(recurrence_obj.exdates) > self.max_exdates:
                raise forms.ValidationError(
                    self.error_messages['max_exdates_exceeded'] % {
                        'limit': self.max_exdates
                    }
                )

        for rrule in recurrence_obj.rrules:
            if rrule.freq not in self.frequencies:
                raise forms.ValidationError(
                    self.error_messages['invalid_frequency'])
        for exrule in recurrence_obj.exrules:
            if exrule.freq not in self.frequencies:
                raise forms.ValidationError(
                    self.error_messages['invalid_frequency'])

        if self.required:
            if not recurrence_obj.rrules and not recurrence_obj.rdates and not recurrence_obj.exdates and not recurrence_obj.exrules:
                raise forms.ValidationError(
                    self.error_messages['recurrence_required']
                )

        return recurrence_obj


_recurrence_javascript_catalog_url = None


def find_recurrence_i18n_js_catalog():
    # used cached version
    global _recurrence_javascript_catalog_url
    if _recurrence_javascript_catalog_url:
        return _recurrence_javascript_catalog_url

    # first try to use the dynamic form of the javascript_catalog view
    if hasattr(i18n, 'javascript_catalog'):
        try:
            return urls.reverse(
                i18n.javascript_catalog, kwargs={'packages': 'recurrence'})
        except urls.NoReverseMatch:
            pass

    # then scan the entire urlconf for a javascript_catalague pattern
    # that manually selects recurrence as one of the packages to include
    def check_urlpatterns(urlpatterns):
        for pattern in urlpatterns:
            if hasattr(pattern, 'url_patterns'):
                match = check_urlpatterns(pattern.url_patterns)
                if match:
                    return match
            elif (hasattr(i18n, 'javascript_catalog') and pattern.callback == i18n.javascript_catalog and
                  'recurrence' in pattern.default_args.get('packages', [])):
                if pattern.name:
                    return urls.reverse(pattern.name)
                else:
                    return urls.reverse(pattern.callback)

    root_urlconf = __import__(settings.ROOT_URLCONF, {}, {}, [''])
    url = check_urlpatterns(root_urlconf.urlpatterns)
    # cache it for subsequent use
    _recurrence_javascript_catalog_url = url
    return url