source: rattail/rattail/importing/rattail.py

Last change on this file was e603b17, checked in by Lance Edgar <ledgar@…>, 7 weeks ago

Add "from same to same" importer base class

for use when a SQLAlchemy database with same schema is used for both host and
local sides of the importer

  • Property mode set to 100644
File size: 12.2 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 FromSQLAlchemySameToSame
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    .. attribute:: direction
134
135       Value is ``'import'`` - see also
136       :attr:`rattail.importing.handlers.ImportHandler.direction`.
137    """
138    dbkey = 'host'
139
140    @property
141    def host_title(self):
142        return "{} ({})".format(self.config.app_title(default="Rattail"), self.dbkey)
143
144    @property
145    def local_title(self):
146        return self.config.node_title()
147
148    def make_host_session(self):
149        return Session(bind=self.config.rattail_engines[self.dbkey])
150
151
152class FromRattailToRattailExport(FromRattailToRattailBase, FromRattailHandler, ToRattailHandler):
153    """
154    Handler for Rattail (local) -> Rattail (other) data export.
155
156    .. attribute:: direction
157
158       Value is ``'export'`` - see also
159       :attr:`rattail.importing.handlers.ImportHandler.direction`.
160    """
161    direction = 'export'
162
163    @property
164    def host_title(self):
165        return self.config.node_title()
166
167    @property
168    def local_title(self):
169        return "{} ({})".format(self.config.app_title(default="Rattail"), self.dbkey)
170
171    def make_session(self):
172        return Session(bind=self.config.rattail_engines[self.dbkey])
173
174
175class FromRattail(FromSQLAlchemySameToSame):
176    """
177    Base class for Rattail -> Rattail data importers.
178    """
179
180
181class PersonImporter(FromRattail, model.PersonImporter):
182    pass
183
184class PersonEmailAddressImporter(FromRattail, model.PersonEmailAddressImporter):
185    pass
186
187class PersonPhoneNumberImporter(FromRattail, model.PersonPhoneNumberImporter):
188    pass
189
190class PersonMailingAddressImporter(FromRattail, model.PersonMailingAddressImporter):
191    pass
192
193class UserImporter(FromRattail, model.UserImporter):
194    pass
195
196class AdminUserImporter(FromRattail, model.AdminUserImporter):
197
198    @property
199    def supported_fields(self):
200        return super(AdminUserImporter, self).supported_fields + [
201            'admin',
202        ]
203
204    def normalize_host_object(self, user):
205        data = super(AdminUserImporter, self).normalize_local_object(user) # sic
206        if 'admin' in self.fields: # TODO: do we really need this, after the above?
207            data['admin'] = self.admin_uuid in [r.role_uuid for r in user._roles]
208        return data
209
210
211class MessageImporter(FromRattail, model.MessageImporter):
212    pass
213
214class MessageRecipientImporter(FromRattail, model.MessageRecipientImporter):
215    pass
216
217class StoreImporter(FromRattail, model.StoreImporter):
218    pass
219
220class StorePhoneNumberImporter(FromRattail, model.StorePhoneNumberImporter):
221    pass
222
223class EmployeeImporter(FromRattail, model.EmployeeImporter):
224    pass
225
226class EmployeeStoreImporter(FromRattail, model.EmployeeStoreImporter):
227    pass
228
229class EmployeeDepartmentImporter(FromRattail, model.EmployeeDepartmentImporter):
230    pass
231
232class EmployeeEmailAddressImporter(FromRattail, model.EmployeeEmailAddressImporter):
233    pass
234
235class EmployeePhoneNumberImporter(FromRattail, model.EmployeePhoneNumberImporter):
236    pass
237
238class ScheduledShiftImporter(FromRattail, model.ScheduledShiftImporter):
239    pass
240
241class WorkedShiftImporter(FromRattail, model.WorkedShiftImporter):
242    pass
243
244class CustomerImporter(FromRattail, model.CustomerImporter):
245    pass
246
247class CustomerGroupImporter(FromRattail, model.CustomerGroupImporter):
248    pass
249
250class CustomerGroupAssignmentImporter(FromRattail, model.CustomerGroupAssignmentImporter):
251    pass
252
253class CustomerPersonImporter(FromRattail, model.CustomerPersonImporter):
254    pass
255
256class CustomerEmailAddressImporter(FromRattail, model.CustomerEmailAddressImporter):
257    pass
258
259class CustomerPhoneNumberImporter(FromRattail, model.CustomerPhoneNumberImporter):
260    pass
261
262class MemberImporter(FromRattail, model.MemberImporter):
263    pass
264
265class MemberEmailAddressImporter(FromRattail, model.MemberEmailAddressImporter):
266    pass
267
268class MemberPhoneNumberImporter(FromRattail, model.MemberPhoneNumberImporter):
269    pass
270
271class VendorImporter(FromRattail, model.VendorImporter):
272    pass
273
274class VendorEmailAddressImporter(FromRattail, model.VendorEmailAddressImporter):
275    pass
276
277class VendorPhoneNumberImporter(FromRattail, model.VendorPhoneNumberImporter):
278    pass
279
280class VendorContactImporter(FromRattail, model.VendorContactImporter):
281    pass
282
283class DepartmentImporter(FromRattail, model.DepartmentImporter):
284    pass
285
286class SubdepartmentImporter(FromRattail, model.SubdepartmentImporter):
287    pass
288
289class CategoryImporter(FromRattail, model.CategoryImporter):
290    pass
291
292class FamilyImporter(FromRattail, model.FamilyImporter):
293    pass
294
295class ReportCodeImporter(FromRattail, model.ReportCodeImporter):
296    pass
297
298class DepositLinkImporter(FromRattail, model.DepositLinkImporter):
299    pass
300
301class TaxImporter(FromRattail, model.TaxImporter):
302    pass
303
304class InventoryAdjustmentReasonImporter(FromRattail, model.InventoryAdjustmentReasonImporter):
305    pass
306
307class BrandImporter(FromRattail, model.BrandImporter):
308    pass
309
310class ProductImporter(FromRattail, model.ProductImporter):
311
312    # TODO...
313    @property
314    def simple_fields(self):
315        fields = super(ProductImporter, self).simple_fields
316        fields.remove('regular_price_uuid')
317        fields.remove('current_price_uuid')
318        return fields
319
320    def query(self):
321        query = super(ProductImporter, self).query()
322
323        # make sure potential unit items (i.e. rows with NULL unit_uuid) come
324        # first, so they will be created before pack items reference them
325        # cf. https://www.postgresql.org/docs/current/static/queries-order.html
326        # cf. https://stackoverflow.com/a/7622046
327        query = query.order_by(self.host_model_class.unit_uuid.desc())
328
329        return query
330
331
332class ProductCodeImporter(FromRattail, model.ProductCodeImporter):
333    pass
334
335class ProductCostImporter(FromRattail, model.ProductCostImporter):
336    pass
337
338class ProductPriceImporter(FromRattail, model.ProductPriceImporter):
339
340    @property
341    def supported_fields(self):
342        return super(ProductPriceImporter, self).supported_fields + self.product_reference_fields
343
344
345class ProductStoreInfoImporter(FromRattail, model.ProductStoreInfoImporter):
346    pass
347
348class ProductVolatileImporter(FromRattail, model.ProductVolatileImporter):
349    pass
350
351
352class ProductImageImporter(FromRattail, model.ProductImageImporter):
353    """
354    Importer for product images.  Note that this uses the "batch" approach
355    because fetching all data up front is not performant when the host/local
356    systems are on different machines etc.
357    """
358
359    def query(self):
360        query = self.host_session.query(self.model_class)\
361                                 .order_by(self.model_class.uuid)
362        return query[self.host_index:self.host_index + self.batch_size]
363
364
365class LabelProfileImporter(FromRattail, model.LabelProfileImporter):
366
367    def query(self):
368        query = super(LabelProfileImporter, self).query()
369
370        if not self.config.getbool('rattail', 'labels.sync_all_profiles', default=False):
371            # only fetch labels from host which are marked as "sync me"
372            query = query .filter(self.model_class.sync_me == True)
373
374        return query.order_by(self.model_class.ordinal)
Note: See TracBrowser for help on using the repository browser.