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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. import urlparse
  2. from django.shortcuts import get_object_or_404
  3. from django.views.generic import (TemplateView, ListView, DetailView,
  4. CreateView, UpdateView, DeleteView,
  5. FormView, RedirectView)
  6. from django.core.urlresolvers import reverse
  7. from django.core.exceptions import ObjectDoesNotExist
  8. from django.http import HttpResponseRedirect, Http404
  9. from django.contrib import messages
  10. from django.utils.translation import ugettext as _
  11. from django.contrib.auth import (authenticate, login as auth_login,
  12. logout as auth_logout)
  13. from django.contrib.auth.forms import PasswordChangeForm
  14. from django.contrib.sites.models import get_current_site
  15. from django.contrib.auth.models import User
  16. from django.conf import settings
  17. from django.db.models import get_model
  18. from oscar.views.generic import PostActionMixin
  19. from oscar.core.loading import get_class, get_profile_class, get_classes
  20. Dispatcher = get_class('customer.utils', 'Dispatcher')
  21. EmailAuthenticationForm, EmailUserCreationForm, SearchByDateRangeForm = get_classes(
  22. 'customer.forms', ['EmailAuthenticationForm', 'EmailUserCreationForm',
  23. 'SearchByDateRangeForm'])
  24. ProfileForm = get_class('customer.forms', 'ProfileForm')
  25. UserAddressForm = get_class('address.forms', 'UserAddressForm')
  26. user_registered = get_class('customer.signals', 'user_registered')
  27. Order = get_model('order', 'Order')
  28. Line = get_model('basket', 'Line')
  29. Basket = get_model('basket', 'Basket')
  30. UserAddress = get_model('address', 'UserAddress')
  31. Email = get_model('customer', 'email')
  32. UserAddress = get_model('address', 'UserAddress')
  33. CommunicationEventType = get_model('customer', 'communicationeventtype')
  34. ProductAlert = get_model('customer', 'ProductAlert')
  35. class LogoutView(RedirectView):
  36. url = '/'
  37. def get(self, request, *args, **kwargs):
  38. auth_logout(request)
  39. response = super(LogoutView, self).get(request, *args, **kwargs)
  40. for cookie in settings.OSCAR_COOKIES_DELETE_ON_LOGOUT:
  41. response.delete_cookie(cookie)
  42. return response
  43. class ProfileUpdateView(FormView):
  44. form_class = ProfileForm
  45. template_name = 'customer/profile_form.html'
  46. def get_form_kwargs(self):
  47. kwargs = super(ProfileUpdateView, self).get_form_kwargs()
  48. kwargs['user'] = self.request.user
  49. return kwargs
  50. def form_valid(self, form):
  51. form.save()
  52. messages.success(self.request, "Profile updated")
  53. return HttpResponseRedirect(self.get_success_url())
  54. def get_success_url(self):
  55. return reverse('customer:summary')
  56. class AccountSummaryView(TemplateView):
  57. template_name = 'customer/profile.html'
  58. def get_context_data(self, **kwargs):
  59. ctx = super(AccountSummaryView, self).get_context_data(**kwargs)
  60. # Delegate data fetching to separate methods so they are easy to
  61. # override.
  62. ctx['addressbook_size'] = self.request.user.addresses.all().count()
  63. ctx['default_shipping_address'] = self.get_default_shipping_address(self.request.user)
  64. ctx['default_billing_address'] = self.get_default_billing_address(self.request.user)
  65. ctx['orders'] = self.get_orders(self.request.user)
  66. ctx['emails'] = self.get_emails(self.request.user)
  67. ctx['alerts'] = self.get_product_alerts(self.request.user)
  68. self.add_profile_fields(ctx)
  69. ctx['active_tab'] = self.request.GET.get('tab', 'profile')
  70. return ctx
  71. def get_orders(self, user):
  72. return Order._default_manager.filter(user=user)[0:5]
  73. def add_profile_fields(self, ctx):
  74. if not hasattr(settings, 'AUTH_PROFILE_MODULE'):
  75. return
  76. try:
  77. profile = self.request.user.get_profile()
  78. except ObjectDoesNotExist:
  79. profile = get_profile_class()()
  80. field_data = []
  81. for field_name in profile._meta.get_all_field_names():
  82. if field_name in ('user', 'id'):
  83. continue
  84. field = profile._meta.get_field(field_name)
  85. if field.choices:
  86. value = getattr(profile, 'get_%s_display' % field_name)()
  87. else:
  88. value = getattr(profile, field_name)
  89. field_data.append({
  90. 'name': getattr(field, 'verbose_name'),
  91. 'value': value,
  92. })
  93. ctx['profile_fields'] = field_data
  94. ctx['profile'] = profile
  95. def post(self, request, *args, **kwargs):
  96. # A POST means an attempt to change the status of an alert
  97. if 'cancel_alert' in request.POST:
  98. return self.cancel_alert(request.POST.get('cancel_alert'))
  99. return super(AccountSummaryView, self).post(request, *args, **kwargs)
  100. def cancel_alert(self, alert_id):
  101. try:
  102. alert = ProductAlert.objects.get(user=self.request.user, pk=alert_id)
  103. except ProductAlert.DoesNotExist:
  104. messages.error(self.request, _("No alert found"))
  105. else:
  106. alert.cancel()
  107. messages.success(self.request, _("Alert cancelled"))
  108. return HttpResponseRedirect(
  109. reverse('customer:summary')+'?tab=alerts'
  110. )
  111. def get_emails(self, user):
  112. return Email.objects.filter(user=user)
  113. def get_product_alerts(self, user):
  114. return ProductAlert.objects.select_related().filter(
  115. user=self.request.user,
  116. date_closed=None,
  117. )
  118. def get_default_billing_address(self, user):
  119. return self.get_user_address(user, is_default_for_billing=True)
  120. def get_default_shipping_address(self, user):
  121. return self.get_user_address(user, is_default_for_shipping=True)
  122. def get_user_address(self, user, **filters):
  123. try:
  124. return user.addresses.get(**filters)
  125. except UserAddress.DoesNotExist:
  126. return None
  127. class AccountRegistrationView(TemplateView):
  128. template_name = 'customer/registration.html'
  129. redirect_field_name = 'next'
  130. registration_prefix = 'registration'
  131. communication_type_code = 'REGISTRATION'
  132. def get_logged_in_redirect(self):
  133. return reverse('customer:summary')
  134. def check_redirect(self, context):
  135. redirect_to = context.get(self.redirect_field_name)
  136. if not redirect_to:
  137. return settings.LOGIN_REDIRECT_URL
  138. netloc = urlparse.urlparse(redirect_to)[1]
  139. if netloc and netloc != self.request.get_host():
  140. return settings.LOGIN_REDIRECT_URL
  141. return redirect_to
  142. def get_context_data(self, *args, **kwargs):
  143. context = super(AccountRegistrationView, self).get_context_data(*args, **kwargs)
  144. redirect_to = self.request.REQUEST.get(self.redirect_field_name, '')
  145. context[self.redirect_field_name] = redirect_to
  146. context['registration_form'] = EmailUserCreationForm(
  147. prefix=self.registration_prefix
  148. )
  149. return context
  150. def send_registration_email(self, user):
  151. code = self.communication_type_code
  152. ctx = {'user': user,
  153. 'site': get_current_site(self.request)}
  154. messages = CommunicationEventType.objects.get_and_render(
  155. code, ctx)
  156. if messages and messages['body']:
  157. Dispatcher().dispatch_user_messages(user, messages)
  158. def get(self, request, *args, **kwargs):
  159. context = self.get_context_data(*args, **kwargs)
  160. if request.user.is_authenticated():
  161. return HttpResponseRedirect(self.get_logged_in_redirect())
  162. self.request.session.set_test_cookie()
  163. return self.render_to_response(context)
  164. def post(self, request, *args, **kwargs):
  165. context = self.get_context_data(*args, **kwargs)
  166. redirect_to = self.check_redirect(context)
  167. registration_form = EmailUserCreationForm(
  168. prefix=self.registration_prefix,
  169. data=request.POST
  170. )
  171. context['registration_form'] = registration_form
  172. if registration_form.is_valid():
  173. self._register_user(registration_form)
  174. return HttpResponseRedirect(redirect_to)
  175. self.request.session.set_test_cookie()
  176. return self.render_to_response(context)
  177. def _register_user(self, form):
  178. """
  179. Register a new user from the data in *form*. If
  180. ``OSCAR_SEND_REGISTRATION_EMAIL`` is set to ``True`` a
  181. registration email will be send to the provided email address.
  182. A new user account is created and the user is then logged in.
  183. """
  184. user = form.save()
  185. if getattr(settings, 'OSCAR_SEND_REGISTRATION_EMAIL', True):
  186. self.send_registration_email(user)
  187. user_registered.send_robust(sender=self, user=user)
  188. try:
  189. user = authenticate(
  190. username=user.email,
  191. password=form.cleaned_data['password1'])
  192. except User.MultipleObjectsReturned:
  193. # Handle race condition where the registration request is made
  194. # multiple times in quick succession. This leads to both requests
  195. # passing the uniqueness check and creating users (as the first one
  196. # hasn't committed when the second one runs the check). We retain
  197. # the first one and delete the dupes.
  198. users = User.objects.filter(email=user.email)
  199. user = users[0]
  200. for u in users[1:]:
  201. u.delete()
  202. auth_login(self.request, user)
  203. if self.request.session.test_cookie_worked():
  204. self.request.session.delete_test_cookie()
  205. class AccountAuthView(AccountRegistrationView):
  206. template_name = 'customer/login_registration.html'
  207. login_prefix = 'login'
  208. def get_context_data(self, *args, **kwargs):
  209. context = super(AccountAuthView, self).get_context_data(*args, **kwargs)
  210. redirect_to = self.request.REQUEST.get(self.redirect_field_name, '')
  211. context[self.redirect_field_name] = redirect_to
  212. context['login_form'] = EmailAuthenticationForm(prefix=self.login_prefix)
  213. context['registration_form'] = EmailUserCreationForm(prefix=self.registration_prefix)
  214. return context
  215. def post(self, request, *args, **kwargs):
  216. context = self.get_context_data(*args, **kwargs)
  217. redirect_to = self.check_redirect(context)
  218. if u'login_submit' in self.request.POST:
  219. login_form = EmailAuthenticationForm(
  220. prefix=self.login_prefix,
  221. data=request.POST
  222. )
  223. if login_form.is_valid():
  224. auth_login(request, login_form.get_user())
  225. if request.session.test_cookie_worked():
  226. request.session.delete_test_cookie()
  227. return HttpResponseRedirect(redirect_to)
  228. context['login_form'] = login_form
  229. if u'registration_submit' in self.request.POST:
  230. registration_form = EmailUserCreationForm(
  231. prefix=self.registration_prefix,
  232. data=request.POST
  233. )
  234. context['registration_form'] = registration_form
  235. if registration_form.is_valid():
  236. self._register_user(registration_form)
  237. return HttpResponseRedirect(redirect_to)
  238. self.request.session.set_test_cookie()
  239. return self.render_to_response(context)
  240. class EmailHistoryView(ListView):
  241. """Customer email history"""
  242. context_object_name = "emails"
  243. template_name = 'customer/email_list.html'
  244. paginate_by = 20
  245. def get_queryset(self):
  246. """Return a customer's orders"""
  247. return Email._default_manager.filter(user=self.request.user)
  248. class EmailDetailView(DetailView):
  249. """Customer order details"""
  250. template_name = "customer/email.html"
  251. context_object_name = 'email'
  252. def get_object(self, queryset=None):
  253. """Return an order object or 404"""
  254. return get_object_or_404(Email, user=self.request.user,
  255. id=self.kwargs['email_id'])
  256. class OrderHistoryView(ListView):
  257. """
  258. Customer order history
  259. """
  260. context_object_name = "orders"
  261. template_name = 'customer/order_list.html'
  262. paginate_by = 20
  263. model = Order
  264. form_class = SearchByDateRangeForm
  265. def get(self, request, *args, **kwargs):
  266. if 'date_from' in request.GET:
  267. self.form = SearchByDateRangeForm(self.request.GET)
  268. if not self.form.is_valid():
  269. self.object_list = self.get_queryset()
  270. ctx = self.get_context_data(object_list=self.object_list)
  271. return self.render_to_response(ctx)
  272. else:
  273. self.form = SearchByDateRangeForm()
  274. return super(OrderHistoryView, self).get(request, *args, **kwargs)
  275. def get_queryset(self):
  276. qs = self.model._default_manager.filter(user=self.request.user)
  277. if self.form.is_bound and self.form.is_valid():
  278. qs = qs.filter(**self.form.get_filters())
  279. return qs
  280. def get_context_data(self, *args, **kwargs):
  281. ctx = super(OrderHistoryView, self).get_context_data(*args, **kwargs)
  282. ctx['form'] = self.form
  283. return ctx
  284. class OrderDetailView(DetailView, PostActionMixin):
  285. """Customer order details"""
  286. model = Order
  287. def get_template_names(self):
  288. return ["customer/order.html"]
  289. def get_object(self, queryset=None):
  290. return get_object_or_404(self.model, user=self.request.user,
  291. number=self.kwargs['order_number'])
  292. def do_reorder(self, order):
  293. """
  294. 'Re-order' a previous order.
  295. This puts the contents of the previous order into your basket
  296. """
  297. basket = self.request.basket
  298. # Convert line attributes into basket options
  299. lines_added = []
  300. warnings = []
  301. # Collect lines to be added to the basket and the warnings
  302. for line in order.lines.all():
  303. if not line.product:
  304. warnings.append(_("'%s' unavailable for re-order") % line.title)
  305. continue
  306. try:
  307. basket_line = basket.lines.get(product=line.product)
  308. except Line.DoesNotExist:
  309. desired_qty = line.quantity
  310. else:
  311. desired_qty = basket_line.quantity + line.quantity
  312. is_available, reason = line.product.is_purchase_permitted(
  313. user=self.request.user,
  314. quantity=desired_qty)
  315. if not is_available:
  316. warnings.append(reason)
  317. continue
  318. lines_added.append(line)
  319. # Check whether the number of items in the basket won't exceed the maximum
  320. total_quantity = sum([line.quantity for line in lines_added])
  321. is_quantity_allowed, reason = basket.is_quantity_allowed(total_quantity)
  322. self.response = HttpResponseRedirect(reverse('customer:order-list'))
  323. if not is_quantity_allowed:
  324. messages.warning(self.request, reason)
  325. return
  326. else:
  327. # Add items to the basket, display warnings
  328. for warning in warnings:
  329. messages.warning(self.request, warning)
  330. for line in lines_added:
  331. options = []
  332. for attribute in line.attributes.all():
  333. if attribute.option:
  334. options.append({'option': attribute.option, 'value': attribute.value})
  335. basket.add_product(line.product, line.quantity, options)
  336. if len(lines_added) > 0:
  337. self.response = HttpResponseRedirect(reverse('basket:summary'))
  338. messages.info(self.request,
  339. _("All available lines from order %s "
  340. "have been added to your basket") % order.number)
  341. class OrderLineView(DetailView, PostActionMixin):
  342. """Customer order line"""
  343. def get_object(self, queryset=None):
  344. """Return an order object or 404"""
  345. order = get_object_or_404(Order, user=self.request.user,
  346. number=self.kwargs['order_number'])
  347. return order.lines.get(id=self.kwargs['line_id'])
  348. def do_reorder(self, line):
  349. self.response = HttpResponseRedirect(reverse('customer:order',
  350. args=(int(self.kwargs['order_number']),)))
  351. basket = self.request.basket
  352. # Check whether basket items quantity won't exceed the maximum
  353. is_quantity_allowed, reason = basket.is_quantity_allowed(line.quantity)
  354. if not is_quantity_allowed:
  355. messages.warning(self.request, reason)
  356. return
  357. if not line.product:
  358. messages.info(self.request, _("This product is no longer available for re-order"))
  359. return
  360. try:
  361. basket_line = basket.lines.get(product=line.product)
  362. except Line.DoesNotExist:
  363. desired_qty = line.quantity
  364. else:
  365. desired_qty = basket_line.quantity + line.quantity
  366. is_available, reason = line.product.is_purchase_permitted(
  367. user=self.request.user,
  368. quantity=desired_qty)
  369. if not is_available:
  370. messages.warning(self.request, reason)
  371. return
  372. # We need to pass response to the get_or_create... method
  373. # as a new basket might need to be created
  374. self.response = HttpResponseRedirect(reverse('basket:summary'))
  375. # Convert line attributes into basket options
  376. options = []
  377. for attribute in line.attributes.all():
  378. if attribute.option:
  379. options.append({'option': attribute.option, 'value': attribute.value})
  380. basket.add_product(line.product, line.quantity, options)
  381. if line.quantity > 1:
  382. msg = _("%(qty)d copies of '%(product)s' have been added to your basket") % {
  383. 'qty': line.quantity, 'product': line.product}
  384. else:
  385. msg = _("'%s' has been added to your basket") % line.product
  386. messages.info(self.request, msg)
  387. class AddressListView(ListView):
  388. """Customer address book"""
  389. context_object_name = "addresses"
  390. template_name = 'customer/address_list.html'
  391. paginate_by = 40
  392. def get_queryset(self):
  393. """Return a customer's addresses"""
  394. return UserAddress._default_manager.filter(user=self.request.user)
  395. class AddressCreateView(CreateView):
  396. form_class = UserAddressForm
  397. mode = UserAddress
  398. template_name = 'customer/address_form.html'
  399. def get_context_data(self, **kwargs):
  400. ctx = super(AddressCreateView, self).get_context_data(**kwargs)
  401. ctx['title'] = _('Add a new address')
  402. return ctx
  403. def form_valid(self, form):
  404. self.object = form.save(commit=False)
  405. self.object.user = self.request.user
  406. self.object.save()
  407. return HttpResponseRedirect(self.get_success_url())
  408. def get_success_url(self):
  409. messages.success(self.request, _("Address saved"))
  410. return reverse('customer:address-list')
  411. class AddressUpdateView(UpdateView):
  412. form_class = UserAddressForm
  413. model = UserAddress
  414. template_name = 'customer/address_form.html'
  415. def get_context_data(self, **kwargs):
  416. ctx = super(AddressUpdateView, self).get_context_data(**kwargs)
  417. ctx['title'] = _('Edit address')
  418. return ctx
  419. def get_queryset(self):
  420. return UserAddress._default_manager.filter(user=self.request.user)
  421. def get_success_url(self):
  422. messages.success(self.request, _("Address saved"))
  423. return reverse('customer:address-detail', kwargs={'pk': self.get_object().pk })
  424. class AddressDeleteView(DeleteView):
  425. model = UserAddress
  426. template_name = "customer/address_delete.html"
  427. def get_queryset(self):
  428. return UserAddress._default_manager.filter(user=self.request.user)
  429. def get_success_url(self):
  430. return reverse('customer:address-list')
  431. class AnonymousOrderDetailView(DetailView):
  432. model = Order
  433. template_name = "customer/anon_order.html"
  434. def get_object(self, queryset=None):
  435. # Check URL hash matches that for order to prevent spoof attacks
  436. order = get_object_or_404(self.model, user=None,
  437. number=self.kwargs['order_number'])
  438. if self.kwargs['hash'] != order.verification_hash():
  439. raise Http404()
  440. return order
  441. class ChangePasswordView(FormView):
  442. form_class = PasswordChangeForm
  443. template_name = 'customer/change_password_form.html'
  444. def get_form_kwargs(self):
  445. kwargs = super(ChangePasswordView, self).get_form_kwargs()
  446. kwargs['user'] = self.request.user
  447. return kwargs
  448. def form_valid(self, form):
  449. form.save()
  450. messages.success(self.request, _("Password updated"))
  451. return HttpResponseRedirect(self.get_success_url())
  452. def get_success_url(self):
  453. return reverse('customer:summary')