source: rattail/rattail/importing/rattail.py @ 2f408c1

Last change on this file since 2f408c1 was 2f408c1, checked in by Lance Edgar <ledgar@…>, 5 months ago

Add ProductVolatile model, for "volatile" product attributes

at least that's the idea...hopefully this table "wins the war" for this concept

  • Property mode set to 100644
File size: 12.9 KB
Line 
1# -*- coding: utf-8; -*-
2################################################################################
3#
4#  Rattail -- Retail Software Framework
5#  Copyright © 2010-2019 Lance Edgar
6#
7#  This file is part of Rattail.
8#
9#  Rattail is free software: you can redistribute it and/or modify it under the
10#  terms of the GNU General Public License as published by the Free Software
11#  Foundation, either version 3 of the License, or (at your option) any later
12#  version.
13#
14#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
15#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17#  details.
18#
19#  You should have received a copy of the GNU General Public License along with
20#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Rattail -> Rattail data import
25"""
26
27from __future__ import unicode_literals, absolute_import
28
29from rattail.db import Session
30from rattail.importing import model
31from rattail.importing.handlers import FromSQLAlchemyHandler, ToSQLAlchemyHandler
32from rattail.importing.sqlalchemy import FromSQLAlchemy
33from rattail.util import OrderedDict
34
35
36class FromRattailHandler(FromSQLAlchemyHandler):
37    """
38    Base class for import handlers which target a Rattail database on the local side.
39    """
40    host_title = "Rattail"
41
42    def make_host_session(self):
43        return Session()
44
45
46class ToRattailHandler(ToSQLAlchemyHandler):
47    """
48    Base class for import handlers which target a Rattail database on the local side.
49    """
50    local_title = "Rattail"
51
52    def make_session(self):
53        kwargs = {}
54        if hasattr(self, 'runas_user'):
55            kwargs['continuum_user'] = self.runas_user
56        return Session(**kwargs)
57
58    def begin_local_transaction(self):
59        self.session = self.make_session()
60        if hasattr(self, 'runas_username') and self.runas_username:
61            self.session.set_continuum_user(self.runas_username)
62
63
64class FromRattailToRattailBase(object):
65    """
66    Common base class for Rattail -> Rattail data import/export handlers.
67    """
68
69    def get_importers(self):
70        importers = OrderedDict()
71        importers['Person'] = PersonImporter
72        importers['PersonEmailAddress'] = PersonEmailAddressImporter
73        importers['PersonPhoneNumber'] = PersonPhoneNumberImporter
74        importers['PersonMailingAddress'] = PersonMailingAddressImporter
75        importers['User'] = UserImporter
76        importers['AdminUser'] = AdminUserImporter
77        importers['Message'] = MessageImporter
78        importers['MessageRecipient'] = MessageRecipientImporter
79        importers['Store'] = StoreImporter
80        importers['StorePhoneNumber'] = StorePhoneNumberImporter
81        importers['Employee'] = EmployeeImporter
82        importers['EmployeeStore'] = EmployeeStoreImporter
83        importers['EmployeeEmailAddress'] = EmployeeEmailAddressImporter
84        importers['EmployeePhoneNumber'] = EmployeePhoneNumberImporter
85        importers['ScheduledShift'] = ScheduledShiftImporter
86        importers['WorkedShift'] = WorkedShiftImporter
87        importers['Customer'] = CustomerImporter
88        importers['CustomerGroup'] = CustomerGroupImporter
89        importers['CustomerGroupAssignment'] = CustomerGroupAssignmentImporter
90        importers['CustomerPerson'] = CustomerPersonImporter
91        importers['CustomerEmailAddress'] = CustomerEmailAddressImporter
92        importers['CustomerPhoneNumber'] = CustomerPhoneNumberImporter
93        importers['Member'] = MemberImporter
94        importers['MemberEmailAddress'] = MemberEmailAddressImporter
95        importers['MemberPhoneNumber'] = MemberPhoneNumberImporter
96        importers['Vendor'] = VendorImporter
97        importers['VendorEmailAddress'] = VendorEmailAddressImporter
98        importers['VendorPhoneNumber'] = VendorPhoneNumberImporter
99        importers['VendorContact'] = VendorContactImporter
100        importers['Department'] = DepartmentImporter
101        importers['EmployeeDepartment'] = EmployeeDepartmentImporter
102        importers['Subdepartment'] = SubdepartmentImporter
103        importers['Category'] = CategoryImporter
104        importers['Family'] = FamilyImporter
105        importers['ReportCode'] = ReportCodeImporter
106        importers['DepositLink'] = DepositLinkImporter
107        importers['Tax'] = TaxImporter
108        importers['InventoryAdjustmentReason'] = InventoryAdjustmentReasonImporter
109        importers['Brand'] = BrandImporter
110        importers['Product'] = ProductImporter
111        importers['ProductCode'] = ProductCodeImporter
112        importers['ProductCost'] = ProductCostImporter
113        importers['ProductPrice'] = ProductPriceImporter
114        importers['ProductStoreInfo'] = ProductStoreInfoImporter
115        importers['ProductVolatile'] = ProductVolatileImporter
116        importers['ProductImage'] = ProductImageImporter
117        importers['LabelProfile'] = LabelProfileImporter
118        return importers
119
120    def get_default_keys(self):
121        keys = self.get_importer_keys()
122        if 'AdminUser' in keys:
123            keys.remove('AdminUser')
124        if 'ProductImage' in keys:
125            keys.remove('ProductImage')
126        return keys
127
128
129class FromRattailToRattailImport(FromRattailToRattailBase, FromRattailHandler, ToRattailHandler):
130    """
131    Handler for Rattail (other) -> Rattail (local) data import.
132    """
133    dbkey = 'host'
134
135    @property
136    def host_title(self):
137        return "{} ({})".format(self.config.app_title(default="Rattail"), self.dbkey)
138
139    @property
140    def local_title(self):
141        return self.config.node_title()
142
143    def make_host_session(self):
144        return Session(bind=self.config.rattail_engines[self.dbkey])
145
146
147class FromRattailToRattailExport(FromRattailToRattailBase, FromRattailHandler, ToRattailHandler):
148    """
149    Handler for Rattail (local) -> Rattail (other) data export.
150    """
151
152    @property
153    def host_title(self):
154        return self.config.node_title()
155
156    @property
157    def local_title(self):
158        return "{} ({})".format(self.config.app_title(default="Rattail"), self.dbkey)
159
160    def make_session(self):
161        return Session(bind=self.config.rattail_engines[self.dbkey])
162
163
164class FromRattail(FromSQLAlchemy):
165    """
166    Base class for Rattail -> Rattail data importers.
167    """
168
169    @property
170    def host_model_class(self):
171        return self.model_class
172
173    @property
174    def supported_fields(self):
175        """
176        We only need to support the simple fields in a Rattail->Rattail import,
177        since all relevant tables should be covered and therefore no need to do
178        crazy foreign key acrobatics etc.
179        """
180        return self.simple_fields
181
182    def query(self):
183        """
184        Leverage the same caching optimizations on both sides, if applicable.
185        """
186        query = super(FromRattail, self).query()
187        if hasattr(self, 'cache_query_options'):
188            options = self.cache_query_options()
189            if options:
190                for option in options:
191                    query = query.options(option)
192        return query
193
194    def normalize_host_object(self, obj):
195        """
196        Normalization should work the same for both sides.
197        """
198        return self.normalize_local_object(obj)
199
200
201class PersonImporter(FromRattail, model.PersonImporter):
202    pass
203
204class PersonEmailAddressImporter(FromRattail, model.PersonEmailAddressImporter):
205    pass
206
207class PersonPhoneNumberImporter(FromRattail, model.PersonPhoneNumberImporter):
208    pass
209
210class PersonMailingAddressImporter(FromRattail, model.PersonMailingAddressImporter):
211    pass
212
213class UserImporter(FromRattail, model.UserImporter):
214    pass
215
216class AdminUserImporter(FromRattail, model.AdminUserImporter):
217
218    @property
219    def supported_fields(self):
220        return super(AdminUserImporter, self).supported_fields + [
221            'admin',
222        ]
223
224    def normalize_host_object(self, user):
225        data = super(AdminUserImporter, self).normalize_local_object(user) # sic
226        if 'admin' in self.fields: # TODO: do we really need this, after the above?
227            data['admin'] = self.admin_uuid in [r.role_uuid for r in user._roles]
228        return data
229
230
231class MessageImporter(FromRattail, model.MessageImporter):
232    pass
233
234class MessageRecipientImporter(FromRattail, model.MessageRecipientImporter):
235    pass
236
237class StoreImporter(FromRattail, model.StoreImporter):
238    pass
239
240class StorePhoneNumberImporter(FromRattail, model.StorePhoneNumberImporter):
241    pass
242
243class EmployeeImporter(FromRattail, model.EmployeeImporter):
244    pass
245
246class EmployeeStoreImporter(FromRattail, model.EmployeeStoreImporter):
247    pass
248
249class EmployeeDepartmentImporter(FromRattail, model.EmployeeDepartmentImporter):
250    pass
251
252class EmployeeEmailAddressImporter(FromRattail, model.EmployeeEmailAddressImporter):
253    pass
254
255class EmployeePhoneNumberImporter(FromRattail, model.EmployeePhoneNumberImporter):
256    pass
257
258class ScheduledShiftImporter(FromRattail, model.ScheduledShiftImporter):
259    pass
260
261class WorkedShiftImporter(FromRattail, model.WorkedShiftImporter):
262    pass
263
264class CustomerImporter(FromRattail, model.CustomerImporter):
265    pass
266
267class CustomerGroupImporter(FromRattail, model.CustomerGroupImporter):
268    pass
269
270class CustomerGroupAssignmentImporter(FromRattail, model.CustomerGroupAssignmentImporter):
271    pass
272
273class CustomerPersonImporter(FromRattail, model.CustomerPersonImporter):
274    pass
275
276class CustomerEmailAddressImporter(FromRattail, model.CustomerEmailAddressImporter):
277    pass
278
279class CustomerPhoneNumberImporter(FromRattail, model.CustomerPhoneNumberImporter):
280    pass
281
282class MemberImporter(FromRattail, model.MemberImporter):
283    pass
284
285class MemberEmailAddressImporter(FromRattail, model.MemberEmailAddressImporter):
286    pass
287
288class MemberPhoneNumberImporter(FromRattail, model.MemberPhoneNumberImporter):
289    pass
290
291class VendorImporter(FromRattail, model.VendorImporter):
292    pass
293
294class VendorEmailAddressImporter(FromRattail, model.VendorEmailAddressImporter):
295    pass
296
297class VendorPhoneNumberImporter(FromRattail, model.VendorPhoneNumberImporter):
298    pass
299
300class VendorContactImporter(FromRattail, model.VendorContactImporter):
301    pass
302
303class DepartmentImporter(FromRattail, model.DepartmentImporter):
304    pass
305
306class SubdepartmentImporter(FromRattail, model.SubdepartmentImporter):
307    pass
308
309class CategoryImporter(FromRattail, model.CategoryImporter):
310    pass
311
312class FamilyImporter(FromRattail, model.FamilyImporter):
313    pass
314
315class ReportCodeImporter(FromRattail, model.ReportCodeImporter):
316    pass
317
318class DepositLinkImporter(FromRattail, model.DepositLinkImporter):
319    pass
320
321class TaxImporter(FromRattail, model.TaxImporter):
322    pass
323
324class InventoryAdjustmentReasonImporter(FromRattail, model.InventoryAdjustmentReasonImporter):
325    pass
326
327class BrandImporter(FromRattail, model.BrandImporter):
328    pass
329
330class ProductImporter(FromRattail, model.ProductImporter):
331
332    # TODO...
333    @property
334    def simple_fields(self):
335        fields = super(ProductImporter, self).simple_fields
336        fields.remove('regular_price_uuid')
337        fields.remove('current_price_uuid')
338        return fields
339
340    def query(self):
341        query = super(ProductImporter, self).query()
342
343        # make sure potential unit items (i.e. rows with NULL unit_uuid) come
344        # first, so they will be created before pack items reference them
345        # cf. https://www.postgresql.org/docs/current/static/queries-order.html
346        # cf. https://stackoverflow.com/a/7622046
347        query = query.order_by(self.host_model_class.unit_uuid.desc())
348
349        return query
350
351
352class ProductCodeImporter(FromRattail, model.ProductCodeImporter):
353    pass
354
355class ProductCostImporter(FromRattail, model.ProductCostImporter):
356    pass
357
358class ProductPriceImporter(FromRattail, model.ProductPriceImporter):
359
360    @property
361    def supported_fields(self):
362        return super(ProductPriceImporter, self).supported_fields + self.product_reference_fields
363
364
365class ProductStoreInfoImporter(FromRattail, model.ProductStoreInfoImporter):
366    pass
367
368class ProductVolatileImporter(FromRattail, model.ProductVolatileImporter):
369    pass
370
371
372class ProductImageImporter(FromRattail, model.ProductImageImporter):
373    """
374    Importer for product images.  Note that this uses the "batch" approach
375    because fetching all data up front is not performant when the host/local
376    systems are on different machines etc.
377    """
378
379    def query(self):
380        query = self.host_session.query(self.model_class)\
381                                 .order_by(self.model_class.uuid)
382        return query[self.host_index:self.host_index + self.batch_size]
383
384
385class LabelProfileImporter(FromRattail, model.LabelProfileImporter):
386
387    def query(self):
388        query = super(LabelProfileImporter, self).query()
389
390        if not self.config.getbool('rattail', 'labels.sync_all_profiles', default=False):
391            # only fetch labels from host which are marked as "sync me"
392            query = query .filter(self.model_class.sync_me == True)
393
394        return query.order_by(self.model_class.ordinal)
Note: See TracBrowser for help on using the repository browser.