Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

generic.py 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. import six
  2. import json
  3. from django import forms
  4. from django.core import validators
  5. from django.core.exceptions import ValidationError
  6. from django.utils.encoding import smart_str
  7. from django.contrib import messages
  8. from django.http import HttpResponseRedirect, HttpResponse
  9. from django.utils.translation import ugettext_lazy as _
  10. from django.views.generic.base import View
  11. import phonenumbers
  12. from oscar.core.phonenumber import PhoneNumber
  13. from six.moves import map
  14. class PostActionMixin(object):
  15. """
  16. Simple mixin to forward POST request that contain a key 'action'
  17. onto a method of form "do_{action}".
  18. This only works with DetailView
  19. """
  20. def post(self, request, *args, **kwargs):
  21. if 'action' in self.request.POST:
  22. model = self.get_object()
  23. # The do_* method is required to do what it needs to with the model
  24. # it is passed, and then to assign the HTTP response to
  25. # self.response.
  26. method_name = "do_%s" % self.request.POST['action'].lower()
  27. if hasattr(self, method_name):
  28. getattr(self, method_name)(model)
  29. return self.response
  30. else:
  31. messages.error(request, _("Invalid form submission"))
  32. return super(PostActionMixin, self).post(request, *args, **kwargs)
  33. class BulkEditMixin(object):
  34. """
  35. Mixin for views that have a bulk editing facility. This is normally in the
  36. form of tabular data where each row has a checkbox. The UI allows a number
  37. of rows to be selected and then some 'action' to be performed on them.
  38. """
  39. action_param = 'action'
  40. # Permitted methods that can be used to act on the selected objects
  41. actions = None
  42. checkbox_object_name = None
  43. def get_checkbox_object_name(self):
  44. if self.checkbox_object_name:
  45. return self.checkbox_object_name
  46. return smart_str(self.model._meta.object_name.lower())
  47. def get_error_url(self, request):
  48. return request.META.get('HTTP_REFERER', '.')
  49. def get_success_url(self, request):
  50. return request.META.get('HTTP_REFERER', '.')
  51. def post(self, request, *args, **kwargs):
  52. # Dynamic dispatch pattern - we forward POST requests onto a method
  53. # designated by the 'action' parameter. The action has to be in a
  54. # whitelist to avoid security issues.
  55. action = request.POST.get(self.action_param, '').lower()
  56. if not self.actions or action not in self.actions:
  57. messages.error(self.request, _("Invalid action"))
  58. return HttpResponseRedirect(self.get_error_url(request))
  59. ids = request.POST.getlist(
  60. 'selected_%s' % self.get_checkbox_object_name())
  61. ids = list(map(int, ids))
  62. if not ids:
  63. messages.error(
  64. self.request,
  65. _("You need to select some %ss")
  66. % self.get_checkbox_object_name())
  67. return HttpResponseRedirect(self.get_error_url(request))
  68. objects = self.get_objects(ids)
  69. return getattr(self, action)(request, objects)
  70. def get_objects(self, ids):
  71. object_dict = self.get_object_dict(ids)
  72. # Rearrange back into the original order
  73. return [object_dict[id] for id in ids]
  74. def get_object_dict(self, ids):
  75. return self.get_queryset().in_bulk(ids)
  76. class ObjectLookupView(View):
  77. """Base view for json lookup for objects"""
  78. def get_query_set(self):
  79. return self.model.objects.all()
  80. def format_object(self, obj):
  81. return {
  82. 'id': obj.pk,
  83. 'text': six.text_type(obj),
  84. }
  85. def initial_filter(self, qs, value):
  86. return qs.filter(pk__in=value.split(','))
  87. def lookup_filter(self, qs, term):
  88. return qs
  89. def paginate(self, qs, page, page_limit):
  90. total = qs.count()
  91. start = (page - 1) * page_limit
  92. stop = start + page_limit
  93. qs = qs[start:stop]
  94. return qs, (page_limit * page < total)
  95. def get_args(self):
  96. GET = self.request.GET
  97. return (GET.get('initial', None),
  98. GET.get('q', None),
  99. int(GET.get('page', 1)),
  100. int(GET.get('page_limit', 20)))
  101. def get(self, request):
  102. self.request = request
  103. qs = self.get_query_set()
  104. initial, q, page, page_limit = self.get_args()
  105. if initial:
  106. qs = self.initial_filter(qs, initial)
  107. more = False
  108. else:
  109. if q:
  110. qs = self.lookup_filter(qs, q)
  111. qs, more = self.paginate(qs, page, page_limit)
  112. return HttpResponse(json.dumps({
  113. 'results': [self.format_object(obj) for obj in qs],
  114. 'more': more,
  115. }), mimetype='application/json')
  116. class PhoneNumberMixin(object):
  117. """
  118. Validation mixin for forms with a phone number, and optionally a country.
  119. It tries to validate the phone number, and on failure tries to validate it
  120. using a hint (the country provided), and treating it as a local number.
  121. It looks for ``self.country``, or ``self.fields['country'].queryset``
  122. """
  123. phone_number = forms.CharField(max_length=32, required=False)
  124. def clean_phone_number(self):
  125. number = self.cleaned_data['phone_number']
  126. # empty
  127. if number in validators.EMPTY_VALUES:
  128. return None
  129. # Check for an international phone format
  130. try:
  131. phone_number = PhoneNumber.from_string(number)
  132. except phonenumbers.NumberParseException:
  133. # Try hinting with the shipping country
  134. if hasattr(self.instance, 'country'):
  135. country = self.instance.country
  136. elif hasattr(self.fields.get('country'), 'queryset'):
  137. country = self.fields['country'].queryset[0]
  138. else:
  139. country = None
  140. if not country:
  141. # There is no shipping country, not a valid international
  142. # number
  143. raise ValidationError(
  144. _(u'This is not a valid international phone format.'))
  145. country = self.cleaned_data.get('country', country)
  146. region_code = country.iso_3166_1_a2
  147. # The PhoneNumber class does not allow specifying
  148. # the region. So we drop down to the underlying phonenumbers
  149. # library, which luckily allows parsing into a PhoneNumber
  150. # instance
  151. try:
  152. phone_number = PhoneNumber.from_string(number,
  153. region=region_code)
  154. if not phone_number.is_valid():
  155. raise ValidationError(
  156. _(u'This is not a valid local phone format for %s.')
  157. % country)
  158. except phonenumbers.NumberParseException:
  159. # Not a valid local or international phone number
  160. raise ValidationError(
  161. _(u'This is not a valid local or international phone'
  162. u' format.'))
  163. return phone_number