Browse Source

Merged ProductUpdateView and ProductCreateView

There's a lot of code duplication between the two. This is an attempt of
merging the two.

* The additional formsets are now easily overriden.
* is_valid logic is more concise
* is_stockrecord_submitted is now more robust
* Fix: Partner and Partner SKU were optional. They're required, only the
  entire form is optional.
* Fix: StockRecordForm errors are now displayed
* StockRecord is now deleted when clearing all fields
master
Maik Hoepfel 13 years ago
parent
commit
a1e75b0640

+ 3
- 4
oscar/apps/dashboard/catalogue/app.py View File

@@ -10,8 +10,7 @@ class CatalogueApplication(Application):
10 10
 
11 11
     product_list_view = views.ProductListView
12 12
     product_create_redirect_view = views.ProductCreateRedirectView
13
-    product_create_view = views.ProductCreateView
14
-    product_update_view = views.ProductUpdateView
13
+    product_createupdate_view = views.ProductCreateUpdateView
15 14
     product_delete_view = views.ProductDeleteView
16 15
 
17 16
     category_list_view = views.CategoryListView
@@ -24,13 +23,13 @@ class CatalogueApplication(Application):
24 23
 
25 24
     def get_urls(self):
26 25
         urlpatterns = patterns('',
27
-            url(r'^products/(?P<pk>\d+)/$', self.product_update_view.as_view(),
26
+            url(r'^products/(?P<pk>\d+)/$', self.product_createupdate_view.as_view(),
28 27
                 name='catalogue-product'),
29 28
             url(r'^products/create/$',
30 29
                 self.product_create_redirect_view.as_view(),
31 30
                 name='catalogue-product-create'),
32 31
             url(r'^products/create/(?P<product_class_id>\d+)/$',
33
-                self.product_create_view.as_view(),
32
+                self.product_createupdate_view.as_view(),
34 33
                 name='catalogue-product-create'),
35 34
             url(r'^products/(?P<pk>\d+)/delete/$',
36 35
                 self.product_delete_view.as_view(),

+ 0
- 5
oscar/apps/dashboard/catalogue/forms.py View File

@@ -66,11 +66,6 @@ class ProductSearchForm(forms.Form):
66 66
 
67 67
 
68 68
 class StockRecordForm(forms.ModelForm):
69
-    partner = forms.ModelChoiceField(queryset=Partner.objects.all(),
70
-                                    required=False,
71
-                                    label=_("Partner"))
72
-    partner_sku = forms.CharField(required=False,
73
-                                  label=_("Partner SKU"))
74 69
 
75 70
     def __init__(self, product_class, *args, **kwargs):
76 71
         self.product_class = product_class

+ 124
- 183
oscar/apps/dashboard/catalogue/views.py View File

@@ -1,6 +1,7 @@
1
+from django.core.exceptions import ObjectDoesNotExist
1 2
 from django.views import generic
2 3
 from django.db.models import get_model
3
-from django.http import HttpResponseRedirect
4
+from django.http import HttpResponseRedirect, Http404
4 5
 from django.contrib import messages
5 6
 from django.core.urlresolvers import reverse
6 7
 from django.utils.translation import ugettext_lazy as _
@@ -93,151 +94,69 @@ class ProductCreateRedirectView(generic.RedirectView):
93 94
                            kwargs={'product_class_id': product_class.id})
94 95
 
95 96
 
96
-class ProductCreateView(generic.CreateView):
97
+class ProductCreateUpdateView(generic.UpdateView):
97 98
     template_name = 'dashboard/catalogue/product_update.html'
98 99
     model = Product
99 100
     context_object_name = 'product'
100
-    form_class = ProductForm
101
-
102
-    def get_context_data(self, **kwargs):
103
-        ctx = super(ProductCreateView, self).get_context_data(**kwargs)
104
-        pclass = self.get_product_class()
105
-        if 'stockrecord_form' not in ctx:
106
-            ctx['stockrecord_form'] = StockRecordForm(pclass)
107
-        if 'category_formset' not in ctx:
108
-            ctx['category_formset'] = ProductCategoryFormSet()
109
-        if 'image_formset' not in ctx:
110
-            ctx['image_formset'] = ProductImageFormSet()
111
-        if 'recommended_formset' not in ctx:
112
-            ctx['recommended_formset'] = ProductRecommendationFormSet()
113
-        ctx['title'] = _('Create new %s product') % pclass.name
114
-        ctx['product_class'] = pclass
115
-        return ctx
116
-
117
-    def get_product_class(self):
118
-        return ProductClass.objects.get(id=self.kwargs['product_class_id'])
119
-
120
-    def get_form_kwargs(self):
121
-        kwargs = super(ProductCreateView, self).get_form_kwargs()
122
-        kwargs['product_class'] = self.get_product_class()
123
-        return kwargs
124 101
 
125
-    def is_stockrecord_submitted(self):
126
-        return len(self.request.POST.get('partner', '')) > 0
127
-
128
-    def get_stockrecord_form(self):
129
-        pclass = self.get_product_class()
130
-        if self.is_stockrecord_submitted():
131
-            return StockRecordForm(pclass, self.request.POST)
132
-
133
-        return StockRecordForm(pclass)
134
-
135
-    def form_invalid(self, form):
136
-        category_formset = ProductCategoryFormSet(self.request.POST)
137
-        image_formset = ProductImageFormSet(self.request.POST, self.request.FILES)
138
-        recommended_formset = ProductRecommendationFormSet(self.request.POST,
139
-                                            self.request.FILES)
140
-
141
-
142
-        messages.error(self.request,
143
-                       _("Your submitted data was not valid - please "
144
-                         "correct the below errors"))
145
-        ctx = self.get_context_data(form=form,
146
-                                    stockrecord_form=self.get_stockrecord_form(),
147
-                                    category_formset=category_formset,
148
-                                    image_formset=image_formset,
149
-                                    recommended_formset=recommended_formset)
150
-        return self.render_to_response(ctx)
151
-
152
-    def form_valid(self, form):
153
-        product = form.save()
154
-        category_formset = ProductCategoryFormSet(self.request.POST,
155
-                                                  instance=product)
156
-        image_formset = ProductImageFormSet(self.request.POST,
157
-                                            self.request.FILES,
158
-                                            instance=product)
159
-        recommended_formset = ProductRecommendationFormSet(self.request.POST,
160
-                                            self.request.FILES,
161
-                                            instance=product)
162
-
163
-        stockrecord_form = self.get_stockrecord_form()
164
-
165
-        if self.is_stockrecord_submitted():
166
-            is_valid = all([stockrecord_form.is_valid(),
167
-                            category_formset.is_valid(),
168
-                            image_formset.is_valid(),
169
-                            recommended_formset.is_valid()])
170
-        else:
171
-            is_valid = all([category_formset.is_valid(),
172
-                            image_formset.is_valid(),
173
-                            recommended_formset.is_valid()])
174
-        if is_valid:
175
-            if self.is_stockrecord_submitted():
176
-                # Save stock record
177
-                stockrecord = stockrecord_form.save(commit=False)
178
-                stockrecord.product = product
179
-                stockrecord.save()
180
-            # Save formsets
181
-            category_formset.save()
182
-            image_formset.save()
183
-            recommended_formset.save()
184
-
185
-            return HttpResponseRedirect(self.get_success_url(product))
186
-
187
-        messages.error(self.request,
188
-                       _("Your submitted data was not valid - please "
189
-                         "correct the below errors"))
190
-
191
-        # Delete product as its relations were not valid
192
-        product.delete()
193
-        ctx = self.get_context_data(form=form,
194
-                                    stockrecord_form=stockrecord_form,
195
-                                    category_formset=category_formset,
196
-                                    image_formset=image_formset,
197
-                                    recommended_formset=recommended_formset)
198
-        return self.render_to_response(ctx)
199
-
200
-    def get_url_with_querystring(self, url):
201
-        url_parts = [url]
202
-        if self.request.GET.urlencode():
203
-            url_parts += [self.request.GET.urlencode()]
204
-        return "?".join(url_parts)
205
-
206
-    def get_success_url(self, product):
207
-        messages.success(self.request, _("Created product '%s'") % product.title)
208
-        url = reverse('dashboard:catalogue-product-list')
209
-        if self.request.POST.get('action') == 'continue':
210
-            url = reverse('dashboard:catalogue-product',
211
-                           kwargs={"pk": product.id})
212
-        return self.get_url_with_querystring(url)
213
-
214
-
215
-class ProductUpdateView(generic.UpdateView):
216
-    template_name = 'dashboard/catalogue/product_update.html'
217
-    model = Product
218
-    context_object_name = 'product'
219 102
     form_class = ProductForm
103
+    category_formset = ProductCategoryFormSet
104
+    image_formset = ProductImageFormSet
105
+    recommendations_formset = ProductRecommendationFormSet
106
+    stockrecord_form = StockRecordForm
107
+
108
+    def get_object(self, queryset=None):
109
+        """
110
+        This parts allows generic.UpdateView to handle creating products as
111
+        well. The only distinction between an UpdateView and a CreateView
112
+        is that self.object is None. We emulate this behavior.
113
+        Additionally, self.product_class is set.
114
+        """
115
+        if 'pk' in self.kwargs:  # UpdateView
116
+            obj = super(ProductCreateUpdateView, self).get_object(queryset)
117
+            self.product_class = obj.product_class
118
+            return obj
119
+        else:  # CreateView
120
+            try:
121
+                product_class_id = self.kwargs.get('product_class_id', None)
122
+                self.product_class = ProductClass.objects.get(
123
+                    id=product_class_id)
124
+            except ObjectDoesNotExist:
125
+                raise Http404
126
+            else:
127
+                return None  # success
220 128
 
221 129
     def get_context_data(self, **kwargs):
222
-        ctx = super(ProductUpdateView, self).get_context_data(**kwargs)
130
+        ctx = super(ProductCreateUpdateView, self).get_context_data(**kwargs)
223 131
         if 'stockrecord_form' not in ctx:
224 132
             ctx['stockrecord_form'] = self.get_stockrecord_form()
225 133
         if 'category_formset' not in ctx:
226
-            ctx['category_formset'] = ProductCategoryFormSet(instance=self.object)
134
+            ctx['category_formset'] = self.category_formset(instance=self.object)
227 135
         if 'image_formset' not in ctx:
228
-            ctx['image_formset'] = ProductImageFormSet(instance=self.object)
136
+            ctx['image_formset'] = self.image_formset(instance=self.object)
229 137
         if 'recommended_formset' not in ctx:
230
-            ctx['recommended_formset'] = ProductRecommendationFormSet(instance=self.object)
231
-        ctx['title'] = ctx['product'].get_title()
138
+            ctx['recommended_formset'] = self.recommendations_formset(instance=self.object)
139
+        if self.object is None:
140
+            ctx['title'] = _('Create new %s product') % self.product_class.name
141
+        else:
142
+            ctx['title'] = ctx['product'].get_title()
232 143
         return ctx
233 144
 
234 145
     def get_form_kwargs(self):
235
-        kwargs = super(ProductUpdateView, self).get_form_kwargs()
236
-        kwargs['product_class'] = self.object.product_class
146
+        kwargs = super(ProductCreateUpdateView, self).get_form_kwargs()
147
+        kwargs['product_class'] = self.product_class
237 148
         return kwargs
238 149
 
239 150
     def is_stockrecord_submitted(self):
240
-        return len(self.request.POST.get('partner', '')) > 0
151
+        """
152
+        Check if there's POST data that matches StockRecordForm field names
153
+        """
154
+        fields = dict(self.stockrecord_form.base_fields.items() +
155
+                      self.stockrecord_form.declared_fields.items())
156
+        for name, field in fields.iteritems():
157
+            if len(self.request.POST.get(name, '')) > 0:
158
+                return True
159
+        return False
241 160
 
242 161
     def get_stockrecord_form(self):
243 162
         """
@@ -246,66 +165,85 @@ class ProductUpdateView(generic.UpdateView):
246 165
         stock record it will be passed into the form as
247 166
         ``instance``.
248 167
         """
249
-        stockrecord = None
250
-        if self.object.has_stockrecord:
168
+        try:
251 169
             stockrecord = self.object.stockrecord
252
-        if not self.is_stockrecord_submitted():
253
-            return StockRecordForm(self.object.product_class,
254
-                                   instance=stockrecord)
255
-        return StockRecordForm(
256
-            self.object.product_class,
257
-            self.request.POST,
170
+        except (AttributeError, StockRecord.DoesNotExist):
171
+            # either self.object is None, or no stockrecord
172
+            stockrecord = None
173
+        return self.stockrecord_form(
174
+            product_class=self.product_class,
175
+            data=self.request.POST if self.is_stockrecord_submitted() else None,
258 176
             instance=stockrecord)
259 177
 
178
+    def form_valid(self, form):
179
+        return self.process_all_forms(form)
180
+
260 181
     def form_invalid(self, form):
261
-        stockrecord_form = self.get_stockrecord_form()
182
+        return self.process_all_forms(form)
262 183
 
263
-        category_formset = ProductCategoryFormSet(self.request.POST,
264
-                                                  instance=self.object)
265
-        image_formset = ProductImageFormSet(self.request.POST,
266
-                                            self.request.FILES,
267
-                                            instance=self.object)
268
-        recommended_formset = ProductRecommendationFormSet(self.request.POST,
269
-                                            self.request.FILES,
270
-                                            instance=self.object)
271
-        ctx = self.get_context_data(form=form,
272
-                                    stockrecord_form=stockrecord_form,
273
-                                    category_formset=category_formset,
274
-                                    image_formset=image_formset,
275
-                                    recommended_formset=recommended_formset)
276
-        return self.render_to_response(ctx)
184
+    def process_all_forms(self, form):
185
+        """
186
+        Short-circuits the regular logic to have one place to have our
187
+        logic to check all forms
188
+        """
189
+        self.creating = self.object is None
190
+        # need to create the product here because the inline forms need it
191
+        # can't use commit=False because ProductForm does not support it
192
+        if self.creating and form.is_valid():
193
+            self.object = form.save()
277 194
 
278
-    def form_valid(self, form):
279 195
         stockrecord_form = self.get_stockrecord_form()
280
-        category_formset = ProductCategoryFormSet(self.request.POST,
281
-                                                  instance=self.object)
282
-        image_formset = ProductImageFormSet(self.request.POST,
283
-                                            self.request.FILES,
284
-                                            instance=self.object)
285
-        recommended_formset = ProductRecommendationFormSet(self.request.POST,
286
-                                            self.request.FILES,
287
-                                            instance=self.object)
196
+        category_formset = self.category_formset(self.request.POST,
197
+                                                 instance=self.object)
198
+        image_formset = self.image_formset(self.request.POST,
199
+                                           self.request.FILES,
200
+                                           instance=self.object)
201
+        recommended_formset = self.recommendations_formset(
202
+            self.request.POST, self.request.FILES, instance=self.object)
203
+
204
+        is_valid = all([
205
+            form.is_valid(),
206
+            category_formset.is_valid(),
207
+            image_formset.is_valid(),
208
+            recommended_formset.is_valid(),
209
+            not self.is_stockrecord_submitted() or stockrecord_form.is_valid(),
210
+            ])
211
+
212
+        if is_valid:
213
+            return self.forms_valid(form, stockrecord_form, category_formset,
214
+                                    image_formset, recommended_formset)
215
+        else:
216
+            # delete the temporary product again
217
+            if self.creating and form.is_valid():
218
+                self.object.delete()
219
+                self.object = None
220
+            return self.forms_invalid(form, stockrecord_form, category_formset,
221
+                                      image_formset, recommended_formset)
222
+
223
+    def forms_valid(self, form, stockrecord_form, category_formset,
224
+                    image_formset, recommended_formset):
288 225
         if self.is_stockrecord_submitted():
289
-            is_valid = all([stockrecord_form.is_valid(),
290
-                            category_formset.is_valid(),
291
-                            image_formset.is_valid(),
292
-                            recommended_formset.is_valid()])
226
+            # Save stock record
227
+            stockrecord = stockrecord_form.save(commit=False)
228
+            stockrecord.product = self.object
229
+            stockrecord.save()
293 230
         else:
294
-            is_valid = all([category_formset.is_valid(),
295
-                            image_formset.is_valid(),
296
-                            recommended_formset.is_valid()])
231
+            # delete it
232
+            if self.object.has_stockrecord:
233
+                self.object.stockrecord.delete()
297 234
 
298
-        if is_valid:
299
-            form.save()
300
-            if self.is_stockrecord_submitted():
301
-                stockrecord = stockrecord_form.save(commit=False)
302
-                stockrecord.product = self.object
303
-                stockrecord.save()
304
-            category_formset.save()
305
-            image_formset.save()
306
-            recommended_formset.save()
307
-            return HttpResponseRedirect(self.get_success_url())
235
+        # Save formsets
236
+        category_formset.save()
237
+        image_formset.save()
238
+        recommended_formset.save()
239
+
240
+        return HttpResponseRedirect(self.get_success_url())
308 241
 
242
+    def forms_invalid(self, form, stockrecord_form, category_formset,
243
+                      image_formset, recommended_formset):
244
+        messages.error(self.request,
245
+                       _("Your submitted data was not valid - please "
246
+                         "correct the below errors"))
309 247
         ctx = self.get_context_data(form=form,
310 248
                                     stockrecord_form=stockrecord_form,
311 249
                                     category_formset=category_formset,
@@ -320,12 +258,15 @@ class ProductUpdateView(generic.UpdateView):
320 258
         return "?".join(url_parts)
321 259
 
322 260
     def get_success_url(self):
323
-        messages.success(self.request, _("Updated product '%s'") %
324
-                         self.object.title)
261
+        if self.creating:
262
+            msg = _("Created product '%s'") % self.object.title
263
+        else:
264
+            msg = _("Updated product '%s'") % self.object.title
265
+        messages.success(self.request, msg)
325 266
         url = reverse('dashboard:catalogue-product-list')
326 267
         if self.request.POST.get('action') == 'continue':
327 268
             url = reverse('dashboard:catalogue-product',
328
-                           kwargs={"pk":self.object.id})
269
+                          kwargs={"pk": self.object.id})
329 270
         return self.get_url_with_querystring(url)
330 271
 
331 272
 
@@ -408,7 +349,7 @@ class CategoryCreateView(CategoryListMixin, generic.CreateView):
408 349
         return ctx
409 350
 
410 351
     def get_success_url(self):
411
-        messages.info(self.request, "Category created successfully")
352
+        messages.info(self.request, _("Category created successfully"))
412 353
         return super(CategoryCreateView, self).get_success_url()
413 354
 
414 355
 
@@ -419,11 +360,11 @@ class CategoryUpdateView(CategoryListMixin, generic.UpdateView):
419 360
 
420 361
     def get_context_data(self, **kwargs):
421 362
         ctx = super(CategoryUpdateView, self).get_context_data(**kwargs)
422
-        ctx['title'] = "Update category '%s'" % self.object.name
363
+        ctx['title'] = _("Update category '%s'") % self.object.name
423 364
         return ctx
424 365
 
425 366
     def get_success_url(self):
426
-        messages.info(self.request, "Category updated successfully")
367
+        messages.info(self.request, _("Category updated successfully"))
427 368
         return super(CategoryUpdateView, self).get_success_url()
428 369
 
429 370
 

+ 7
- 16
oscar/templates/oscar/dashboard/catalogue/product_update.html View File

@@ -40,7 +40,7 @@
40 40
                     {% if product %}
41 41
                     <li class="active"><a href="#overview" data-toggle="tab">{% trans 'Overview' %}</a></li>
42 42
                     {% endif %}
43
-                    <li {% if not product %}class="active"{% endif %}><a href="#product_details" data-toggle="tab">{% trans 'Product details' %}</a></li>
43
+                    <li{% if not product %} class="active"{% endif %}><a href="#product_details" data-toggle="tab">{% trans 'Product details' %}</a></li>
44 44
                     <li><a href="#product_recommended" data-toggle="tab">{% trans 'Recommended Products' %}</a></li>
45 45
                     {% with variants=product.variants.all %}
46 46
                     {% if variants|length > 0 %}
@@ -326,29 +326,20 @@
326 326
                             {% for field in stockrecord_form %}
327 327
                                 {% if "partner" in field.id_for_label %}
328 328
                                     <td>
329
-                                        {% if field.is_hidden %}
330
-                                            {{ field }}
331
-                                        {% else %}
332
-                                            {{ field }}
333
-                                        {% endif %}
329
+                                        {{ field }}
330
+                                        {{ field.errors }}
334 331
                                     </td>
335 332
                                 {% endif %}
336 333
                                 {% if "price" in field.id_for_label %}
337 334
                                     <td>
338
-                                        {% if field.is_hidden %}
339
-                                            {{ field }}
340
-                                        {% else %}
341
-                                            {{ field }}
342
-                                        {% endif %}
335
+                                        {{ field }}
336
+                                        {{ field.errors }}
343 337
                                     </td>
344 338
                                 {% endif %}
345 339
                                 {% if "stock" in field.id_for_label %}
346 340
                                     <td>
347
-                                        {% if field.is_hidden %}
348
-                                            {{ field }}
349
-                                        {% else %}
350
-                                            {{ field }}
351
-                                        {% endif %}
341
+                                        {{ field }}
342
+                                        {{ field.errors }}
352 343
                                     </td>
353 344
                                 {% endif %}
354 345
                             {% endfor %}

Loading…
Cancel
Save