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

generic.py 7.2KB

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