source: rattail/rattail/importing/rattail.py @ 3336da3

Last change on this file since 3336da3 was 20e3d52, checked in by Lance Edgar <ledgar@…>, 8 months ago

Add support for importing member, member contact data

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