├── gmerchant ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── update_inventory.py │ │ ├── upload_catalogue.py │ │ ├── import_google_categories.py │ │ └── migrate_to_new_schema.py ├── migrations │ ├── __init__.py │ ├── 0002_auto__chg_field_apiservicecredentials_client_email.py │ ├── 0003_auto__add_googlecategory.py │ ├── 0001_initial.py │ ├── 0007_auto__add_field_googleproduct_google_shopping_updated.py │ ├── 0006_auto__add_field_googleproduct_publish_google_shopping__add_field_googl.py │ ├── 0004_add_model_GoogleProduct.py │ └── 0005_auto__add_field_googleproduct_product_upc__add_index_googleproduct_goo.py ├── views.py ├── client │ ├── __init__.py │ ├── conf.py │ ├── api.py │ └── products.py ├── settings.py ├── tests.py ├── __init__.py ├── admin.py └── models.py ├── requirements.txt ├── .gitignore ├── README.md └── LICENSE /gmerchant/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gmerchant/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gmerchant/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gmerchant/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /gmerchant/client/__init__.py: -------------------------------------------------------------------------------- 1 | from products import * 2 | -------------------------------------------------------------------------------- /gmerchant/settings.py: -------------------------------------------------------------------------------- 1 | 2 | ACCOUNT_ID = 1017329147092 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bleach 2 | pycrypto 3 | google-api-python-client 4 | -------------------------------------------------------------------------------- /gmerchant/client/conf.py: -------------------------------------------------------------------------------- 1 | from django.contrib.sites.models import Site 2 | 3 | MAX_PAGE_SIZE = 50 4 | BATCH_SIZE = 50 5 | BRAND = "Example Brand" 6 | PREFIX = "example#" 7 | PROTOCOL = "http://" 8 | try: 9 | SITE = Site.objects.first().domain 10 | except: 11 | SITE = "www.example.com" 12 | SITE_ROOT = PROTOCOL + SITE -------------------------------------------------------------------------------- /gmerchant/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /gmerchant/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 MediaClash Limited. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | __author__ = 'ben.field@mediaclash.co.uk (Ben Field)' 16 | -------------------------------------------------------------------------------- /gmerchant/management/commands/update_inventory.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand, CommandError 2 | 3 | from local_shop.gmerchant.models import GoogleMerchantAccount 4 | 5 | class GMAException(CommandError): 6 | pass 7 | 8 | class Command(BaseCommand): 9 | help = 'Synchronizes Inventory with Google Shopping.' 10 | 11 | def handle(self, *args, **options): 12 | self.sync_inventory() 13 | print "Complete!" 14 | 15 | def sync_inventory(self): 16 | self.gma = gma = GoogleMerchantAccount.objects.first() 17 | 18 | if not gma: 19 | raise GMAException("You must set up a Google Merchant Account in the admin first.") 20 | 21 | else: 22 | gma.init_client() 23 | gma.update_inventory() -------------------------------------------------------------------------------- /gmerchant/management/commands/upload_catalogue.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand, CommandError 2 | 3 | from local_shop.gmerchant.models import GoogleMerchantAccount 4 | 5 | class GMAException(CommandError): 6 | pass 7 | 8 | class Command(BaseCommand): 9 | help = 'Synchronizes Product Catalogue with Google Shopping.' 10 | 11 | def handle(self, *args, **options): 12 | self.sync_products() 13 | print "Complete!" 14 | 15 | def sync_products(self): 16 | self.gma = gma = GoogleMerchantAccount.objects.first() 17 | 18 | if not gma: 19 | raise GMAException("You must set up a Google Merchant Account in the admin first.") 20 | 21 | else: 22 | gma.init_client() 23 | gma.refresh_catalogue() -------------------------------------------------------------------------------- /gmerchant/client/api.py: -------------------------------------------------------------------------------- 1 | from oauth2client.client import SignedJwtAssertionCredentials 2 | from httplib2 import Http 3 | 4 | 5 | class APIScope(object): 6 | 7 | scope = "" 8 | 9 | def serverOAuthCredentials(self,app): 10 | 11 | #import pdb; pdb.set_trace() 12 | 13 | 14 | client_email = app.client_email 15 | with open(app.private_key_file.path) as f: 16 | private_key = f.read() 17 | 18 | credentials = SignedJwtAssertionCredentials(client_email, private_key, 19 | self.scope) 20 | 21 | return credentials 22 | 23 | def serverAuthorisation(self,app): 24 | creds = self.serverOAuthCredentials(app) 25 | http_auth = creds.authorize(Http()) 26 | 27 | return http_auth 28 | 29 | 30 | class Content(APIScope): 31 | scope = "https://www.googleapis.com/auth/content" 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /gmerchant/management/commands/import_google_categories.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from django.core.management.base import BaseCommand, CommandError 4 | 5 | from local_shop.gmerchant.models import GoogleCategory 6 | 7 | CATEGORY_SOURCE = "http://www.google.com/basepages/producttype/taxonomy.en-US.txt" 8 | 9 | class Command(BaseCommand): 10 | help = 'Fetches the google product category list and stores them in the DB' 11 | 12 | 13 | def handle(self, *args, **options): 14 | 15 | req = requests.get(CATEGORY_SOURCE) 16 | raw_categories = req.text 17 | 18 | #import pdb; pdb.set_trace() 19 | 20 | self.build_categories(raw_categories) 21 | 22 | 23 | def build_categories(self,raw): 24 | cat_list = raw.split("\n") 25 | cat_list.pop(0) # Drop the comment in the header. 26 | 27 | for idx,val in enumerate(cat_list): 28 | cat, created = GoogleCategory.objects.get_or_create(source_idx=idx,name=val) 29 | if created: 30 | cat.save() 31 | 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | django-oscar-gmerchant 2 | ====================== 3 | 4 | Integration with Google Merchant Center and Django Oscar using the v2 Shopping API 5 | 6 | THIS IS STILL AS WORK IN PROGRESS. 7 | 8 | It should however be fairly straight forward to use. 9 | Simply add to your post-oscar applications list and you should be on your way. 10 | 11 | If you receive warnings about the ProductAdminClass, then you should look at changing 12 | the ProductAdmin that is being modified inside of gmerchant/admin to point directly at 13 | your-likely-to-have-been modified class. 14 | 15 | 16 | USAGE 17 | ===== 18 | 19 | 1. Install. 20 | 2. Run import_google_categories. 21 | 3. Add a Google Merchant account to your admin. 22 | 4. Add Google Merchant Records to the products you wish publish. 23 | 5. Run upload_catalogue using a cronjob. 24 | 25 | Contributions welcome. 26 | 27 | 28 | IDEAS / TBD 29 | =========== 30 | 31 | * Add post-save signals to Product model save to trigger updates to single product records. 32 | * Complete integration with "Store Inventory Managment". 33 | * Add Oscar Dashboard integration. 34 | * Improve Merchant account handling. 35 | 36 | Acknowledgments 37 | =============== 38 | 39 | This project borrows heavily from the sample code supplied by [Google](https://github.com/googleads/googleads-shopping-samples/tree/master/python) 40 | -------------------------------------------------------------------------------- /gmerchant/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.conf import settings 3 | from models import GoogleMerchantAccount, APIServiceCredentials, GoogleCategory, GoogleProduct 4 | from oscar.core.loading import get_model, get_class 5 | 6 | 7 | Product = get_model("catalogue","Product") 8 | ProductAdmin = get_class("catalogue.admin","ProductAdmin") 9 | # First, un-register the Existing Product Admin 10 | 11 | admin.site.unregister(Product) 12 | 13 | class GoogleProductDetailsAdmin(admin.StackedInline): 14 | model = GoogleProduct 15 | extra = 0 16 | max_num = 1 #Lazy way to enforce Singleton-like behaviour. 17 | readonly_fields=('google_shopping_created','google_shopping_updated','google_shopping_id',) 18 | exclude = ("google_product","product_upc") 19 | 20 | class GoogleExtendedProductAdmin(ProductAdmin): 21 | inlines = ProductAdmin.inlines + [GoogleProductDetailsAdmin,] 22 | 23 | 24 | #Now we re-register the Product admin class 25 | admin.site.register(Product, GoogleExtendedProductAdmin) 26 | 27 | 28 | class GoogleCategoryAdmin(admin.ModelAdmin): 29 | search_fields = ['name',] 30 | 31 | 32 | 33 | 34 | admin.site.register(GoogleMerchantAccount) 35 | admin.site.register(APIServiceCredentials) 36 | admin.site.register(GoogleCategory, GoogleCategoryAdmin) 37 | 38 | # This isn't necessary to seen during normal operations 39 | if settings.DEBUG: 40 | admin.site.register(GoogleProduct) 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Ben Field 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of django-oscar-gmerchant nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /gmerchant/management/commands/migrate_to_new_schema.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand, CommandError 2 | 3 | from local_shop.gmerchant.models import GoogleMerchantAccount, GoogleProduct 4 | 5 | from oscar.core.loading import get_class, get_model 6 | 7 | Product = get_model("catalogue","Product") 8 | 9 | class GMerchantMigrationError(CommandError): 10 | pass 11 | 12 | class Command(BaseCommand): 13 | help = 'Migrates data from Product model to updated GoogleProduct Model' 14 | 15 | def handle(self, *args, **options): 16 | self.test_migration() 17 | self.migrate_data() 18 | print "Complete!" 19 | 20 | def test_migration(self): 21 | #Is Migration Necessary? 22 | p = Product() 23 | if hasattr(p,"google_shopping_description"): 24 | return True 25 | else: 26 | raise GMerchantMigrationError("You don't need to run this.") 27 | #Test whether or not we can do the migration. 28 | t = GoogleProduct() 29 | if hasattr(t,"google_product_updated"): 30 | return True 31 | else: 32 | raise GMerchantMigrationError("You need to migrate your gmerchant application first.") 33 | 34 | def migrate_data(self): 35 | 36 | gproducts = GoogleProduct.objects.all().select_related() 37 | for p in gproducts: 38 | try: 39 | p.google_shopping_description = p.product.google_shopping_description 40 | p.publish_google_shopping = p.product.publish_google_shopping 41 | p.google_taxonomy = p.product.google_taxonomy 42 | 43 | p.save() 44 | except: 45 | print "Couldn't migrate %s" % p.id 46 | 47 | else: 48 | print "Updated: %s" % p.id -------------------------------------------------------------------------------- /gmerchant/migrations/0002_auto__chg_field_apiservicecredentials_client_email.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | 12 | # Changing field 'APIServiceCredentials.client_email' 13 | db.alter_column(u'gmerchant_apiservicecredentials', 'client_email', self.gf('django.db.models.fields.EmailField')(max_length=128)) 14 | 15 | def backwards(self, orm): 16 | 17 | # Changing field 'APIServiceCredentials.client_email' 18 | db.alter_column(u'gmerchant_apiservicecredentials', 'client_email', self.gf('django.db.models.fields.EmailField')(max_length=75)) 19 | 20 | models = { 21 | u'gmerchant.apiservicecredentials': { 22 | 'Meta': {'object_name': 'APIServiceCredentials'}, 23 | 'application_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 24 | 'client_email': ('django.db.models.fields.EmailField', [], {'max_length': '128', 'blank': 'True'}), 25 | 'client_id': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 26 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'private_key_ciphertext': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 28 | 'private_key_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 29 | 'private_key_phrase': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}) 30 | }, 31 | u'gmerchant.googlemerchantaccount': { 32 | 'Meta': {'object_name': 'GoogleMerchantAccount'}, 33 | 'account_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 34 | 'account_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 35 | 'catalogue_imported': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 36 | 'credentials': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.APIServiceCredentials']", 'null': 'True', 'blank': 'True'}), 37 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 38 | } 39 | } 40 | 41 | complete_apps = ['gmerchant'] -------------------------------------------------------------------------------- /gmerchant/migrations/0003_auto__add_googlecategory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'GoogleCategory' 12 | db.create_table(u'gmerchant_googlecategory', ( 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('name', self.gf('django.db.models.fields.CharField')(max_length=512)), 15 | ('source_idx', self.gf('django.db.models.fields.IntegerField')(db_index=True)), 16 | )) 17 | db.send_create_signal(u'gmerchant', ['GoogleCategory']) 18 | 19 | 20 | def backwards(self, orm): 21 | # Deleting model 'GoogleCategory' 22 | db.delete_table(u'gmerchant_googlecategory') 23 | 24 | 25 | models = { 26 | u'gmerchant.apiservicecredentials': { 27 | 'Meta': {'object_name': 'APIServiceCredentials'}, 28 | 'application_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 29 | 'client_email': ('django.db.models.fields.EmailField', [], {'max_length': '128', 'blank': 'True'}), 30 | 'client_id': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 31 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 32 | 'private_key_ciphertext': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 33 | 'private_key_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 34 | 'private_key_phrase': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}) 35 | }, 36 | u'gmerchant.googlecategory': { 37 | 'Meta': {'object_name': 'GoogleCategory'}, 38 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 39 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), 40 | 'source_idx': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) 41 | }, 42 | u'gmerchant.googlemerchantaccount': { 43 | 'Meta': {'object_name': 'GoogleMerchantAccount'}, 44 | 'account_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 45 | 'account_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 46 | 'catalogue_imported': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 47 | 'credentials': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.APIServiceCredentials']", 'null': 'True', 'blank': 'True'}), 48 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 49 | } 50 | } 51 | 52 | complete_apps = ['gmerchant'] -------------------------------------------------------------------------------- /gmerchant/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'APIServiceCredentials' 12 | db.create_table(u'gmerchant_apiservicecredentials', ( 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('application_name', self.gf('django.db.models.fields.CharField')(max_length=256)), 15 | ('client_id', self.gf('django.db.models.fields.CharField')(max_length=128)), 16 | ('client_email', self.gf('django.db.models.fields.EmailField')(max_length=75, blank=True)), 17 | ('private_key_file', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True, blank=True)), 18 | ('private_key_ciphertext', self.gf('django.db.models.fields.TextField')(blank=True)), 19 | ('private_key_phrase', self.gf('django.db.models.fields.CharField')(max_length=512)), 20 | )) 21 | db.send_create_signal(u'gmerchant', ['APIServiceCredentials']) 22 | 23 | # Adding model 'GoogleMerchantAccount' 24 | db.create_table(u'gmerchant_googlemerchantaccount', ( 25 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 26 | ('account_name', self.gf('django.db.models.fields.CharField')(max_length=128)), 27 | ('account_id', self.gf('django.db.models.fields.CharField')(max_length=64)), 28 | ('credentials', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['gmerchant.APIServiceCredentials'], null=True, blank=True)), 29 | ('catalogue_imported', self.gf('django.db.models.fields.BooleanField')(default=True)), 30 | )) 31 | db.send_create_signal(u'gmerchant', ['GoogleMerchantAccount']) 32 | 33 | 34 | def backwards(self, orm): 35 | # Deleting model 'APIServiceCredentials' 36 | db.delete_table(u'gmerchant_apiservicecredentials') 37 | 38 | # Deleting model 'GoogleMerchantAccount' 39 | db.delete_table(u'gmerchant_googlemerchantaccount') 40 | 41 | 42 | models = { 43 | u'gmerchant.apiservicecredentials': { 44 | 'Meta': {'object_name': 'APIServiceCredentials'}, 45 | 'application_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 46 | 'client_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 47 | 'client_id': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 48 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 49 | 'private_key_ciphertext': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 50 | 'private_key_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 51 | 'private_key_phrase': ('django.db.models.fields.CharField', [], {'max_length': '512'}) 52 | }, 53 | u'gmerchant.googlemerchantaccount': { 54 | 'Meta': {'object_name': 'GoogleMerchantAccount'}, 55 | 'account_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 56 | 'account_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 57 | 'catalogue_imported': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 58 | 'credentials': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.APIServiceCredentials']", 'null': 'True', 'blank': 'True'}), 59 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 60 | } 61 | } 62 | 63 | complete_apps = ['gmerchant'] -------------------------------------------------------------------------------- /gmerchant/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.text import slugify 3 | 4 | from local_shop.catalogue.models import Product 5 | 6 | from .client import ShoppingClient 7 | #p12-key_pw = notasecret 8 | 9 | def key_upload(instance,filename): 10 | return "/".join(['uploads','temp','keys',slugify(instance.application_name),filename]) 11 | 12 | class APIServiceCredentials(models.Model): 13 | """ 14 | This model is used for configuring the credentials required for communicating with the G-API. 15 | """ 16 | application_name = models.CharField(max_length=256) 17 | 18 | client_id = models.CharField(max_length=128) 19 | client_email = models.EmailField(blank=True,max_length=128) 20 | 21 | private_key_file = models.FileField(upload_to=key_upload,help_text="This is deleted immediately after reading and encrypting.",blank=True,null=True) 22 | 23 | private_key_ciphertext = models.TextField(blank=True) 24 | private_key_phrase = models.CharField(max_length=512,help_text="This isn't stored in plaintext after saving.",blank=True) 25 | 26 | def __unicode__(self): 27 | return self.application_name 28 | 29 | class GoogleMerchantAccount(models.Model): 30 | account_name = models.CharField(max_length=128) 31 | account_id = models.CharField(max_length=64) 32 | 33 | credentials = models.ForeignKey('APIServiceCredentials',null=True,blank=True) 34 | 35 | catalogue_imported = models.BooleanField(default=True) 36 | 37 | 38 | client = None 39 | 40 | def __unicode__(self): 41 | return self.account_name + " " + self.account_id 42 | 43 | def init_client(self): 44 | if not self.client: 45 | self.client = ShoppingClient(app=self) 46 | 47 | def fetch_catalogue(self): 48 | self.init_client() 49 | self.client.listProducts() 50 | 51 | def insert_test(self): 52 | #This is for testing. 53 | p = GoogleProduct.objects.filter(publish_google_shopping=True, 54 | stockrecords__num_in_stock__gte=1) 55 | if p: 56 | self.insert_product(p[0]) 57 | 58 | def insert_product(self,product): 59 | self.init_client() 60 | if product.in_stock: 61 | self.client.insertProduct(product) 62 | else: 63 | raise AttributeError("This product is not available to be purchased") 64 | 65 | def upload_catalogue(self): 66 | self.init_client() 67 | # Grab products that aren't already on Google. 68 | p = GoogleProduct.objects.filter(publish_google_shopping=True, 69 | product__stockrecords__num_in_stock__gte=1).exclude(google_shopping_id!=None) 70 | 71 | if len(p) > 0: 72 | self.client.batchInsertProducts(p) 73 | else: 74 | raise ValueError("There aren't any products that are suitable to upload") 75 | 76 | def refresh_catalogue(self): 77 | self.init_client() 78 | # Grab all products that are already on Google. 79 | p = GoogleProduct.objects.filter(publish_google_shopping=True, 80 | product__stockrecords__num_in_stock__gte=1).exclude(google_shopping_id=None) 81 | 82 | if len(p) > 0: 83 | self.client.batchInsertProducts(p) 84 | else: 85 | raise ValueError("There aren't any products that are suitable to upload") 86 | 87 | def update_inventory(self): 88 | self.init_client() 89 | p = GoogleProduct.objects.all().select_related() 90 | if len(p) > 0: 91 | self.client.batchUpdate(p) 92 | else: 93 | raise ValueError("There aren't any products that are suitable for updating") 94 | 95 | 96 | class GoogleCategory(models.Model): 97 | name = models.CharField(max_length=512) 98 | source_idx = models.IntegerField(db_index=True) 99 | 100 | def __unicode__(self): 101 | return self.name 102 | 103 | 104 | class GoogleProduct(models.Model): 105 | class Meta: 106 | verbose_name = "Google Merchant Records" 107 | verbose_name_plural = "Google Merchant Records" 108 | 109 | product = models.ForeignKey(Product) 110 | 111 | publish_google_shopping = models.BooleanField(default=False) 112 | google_taxonomy = models.ForeignKey("gmerchant.GoogleCategory",blank=True,null=True) 113 | google_shopping_description = models.TextField(null=True, blank=True) 114 | 115 | product_upc = models.CharField(max_length=32,blank=True,null=True,db_index=True,editable=False) 116 | google_shopping_id = models.CharField(max_length=128,blank=True,null=True,db_index=True) 117 | google_shopping_created = models.DateTimeField(blank=True,null=True) 118 | google_shopping_updated = models.DateTimeField(blank=True,null=True) 119 | 120 | def __unicode__(self): 121 | return str(self.product.upc) or "" + " - " + self.google_shopping_id or "" 122 | 123 | 124 | -------------------------------------------------------------------------------- /gmerchant/client/products.py: -------------------------------------------------------------------------------- 1 | import bleach 2 | import time 3 | 4 | from datetime import datetime 5 | 6 | from .conf import * 7 | from .api import Content 8 | from apiclient.http import BatchHttpRequest 9 | from oauth2client import client 10 | from googleapiclient import discovery 11 | from oscar.core.loading import get_model 12 | from django.utils.encoding import smart_text 13 | 14 | 15 | Product = get_model('catalogue','Product') 16 | 17 | unique_id_increment = 0 18 | 19 | 20 | 21 | def get_unique_id(gprod): 22 | """Generates a unique ID. 23 | 24 | The ID is based on the current UNIX timestamp and a runtime increment. 25 | 26 | Returns: 27 | A unique string. 28 | """ 29 | if gprod.google_shopping_id: 30 | #Return shopping ID from DB if one is set. 31 | return gprod.google_shopping_id 32 | 33 | global unique_id_increment 34 | if unique_id_increment is None: 35 | unique_id_increment = 0 36 | unique_id_increment += 1 37 | return "%s#%d%d" % (PREFIX,int(time.time()), unique_id_increment) 38 | 39 | def chunks(l, n): 40 | #Yield successive n-sized chunks from l. 41 | for i in xrange(0, len(l), n): 42 | yield l[i:i+n] 43 | 44 | def warn_exp_token(): 45 | print ('The credentials have been revoked or expired, please re-run the ' 46 | 'application to re-authorize') 47 | 48 | 49 | def resolve_availability(product): 50 | # Borrowed from Oscar 0.6~ish 51 | if product.is_group: 52 | # If any one of this product's variants is available, then we treat 53 | # this product as available. 54 | for variant in product.variants.select_related('stockrecord').all(): 55 | if variant.is_available_to_buy: 56 | return True 57 | return False 58 | if not product.get_product_class().track_stock: 59 | return True 60 | return product.has_stockrecord and product.stockrecord.is_available_to_buy 61 | 62 | def resolve_google_availability(product): 63 | # Returns a Google Shopping API compatible availability message. 64 | if resolve_availability(product): 65 | return "in stock" 66 | else: 67 | return "out of stock" 68 | 69 | class ShoppingClient(object): 70 | 71 | app = None 72 | service = None 73 | merchant_id = "" 74 | 75 | def __init__(self,*args,**kwargs): 76 | self.app = kwargs.get("app",None) 77 | if not self.app: 78 | raise AttributeError("You must assign an app to use this.") 79 | else: 80 | 81 | self.merchant_id = self.app.account_id 82 | content_scheme = Content() 83 | self.http_auth = content_scheme.serverAuthorisation(self.app.credentials) 84 | self.service = discovery.build('content', 'v2', http=self.http_auth) 85 | 86 | 87 | def batchUpdateInventory(self,gproduct_qset): 88 | GoogleProduct = get_model('gmerchant','GoogleProduct') 89 | 90 | def product_updated(request_id, unused_response, exception): 91 | if exception is not None: 92 | # Do something with the exception. 93 | print 'There was an error: ' + str(exception) 94 | else: 95 | gp = GoogleProduct.objects.get(google_shopping_id=offer_id) 96 | gp.google_shopping_updated = datetime.now() 97 | gp.save() 98 | print 'Request ID: %s - Product was updated.' % (str(request_id),) 99 | 100 | merchant_id = self.merchant_id 101 | 102 | batch = BatchHttpRequest(callback=product_updated) 103 | 104 | for prod in gproduct_qset: 105 | product = prod.product 106 | new_status = { 107 | #Update the price of the item 108 | 'price' : {'value': str(product.stockrecords.first().price_incl_tax), 'currency': 'GBP'}, 109 | 'description': len(product.google_shopping_description) > 0 and bleach.clean(smart_text(product.google_shopping_description),strip=True) or bleach.clean(smart_text(product.parent.google_shopping_description),strip=True), 110 | 'link': SITE_ROOT + product.get_absolute_url(), 111 | 'imageLink': product.get_first_image_url(), 112 | #Is it in stock? 113 | 'availability': resolve_google_availability(product), 114 | } 115 | # Add product update to the batch. 116 | batch.add(self.service.inventory().set( 117 | merchantId=merchant_id, 118 | productId=prod.google_shopping_id, 119 | 120 | body=new_status)) 121 | try: 122 | batch.execute() 123 | 124 | except client.AccessTokenRefreshError: 125 | warn_exp_token() 126 | 127 | def buildProduct(self,gprod): 128 | product = gprod.product 129 | 130 | offer_id = get_unique_id(gprod) 131 | product_data = { 132 | 'offerId': offer_id, 133 | 'title': smart_text(product.title), 134 | 'description': len(gprod.google_shopping_description) > 0 and bleach.clean(smart_text(gprod.google_shopping_description),strip=True) or bleach.clean(smart_text(gprod.parent.google_shopping_description),strip=True), 135 | 'link': SITE_ROOT + product.get_absolute_url(), 136 | 'imageLink': product.get_first_image_url(), 137 | 'brand': BRAND, 138 | 'contentLanguage': 'en', 139 | 'targetCountry': 'UK', 140 | 'channel': 'online', 141 | 'availability': resolve_google_availability(product), 142 | 'condition': 'new', 143 | 'googleProductCategory': smart_text(gprod.google_taxonomy.name), 144 | 'mpn': product.upc, 145 | 'price': {'value': str(product.stockrecords.first().price_incl_tax), 'currency': 'GBP'}, 146 | 'shipping': [{ 147 | 'country': 'UK', 148 | 'service': 'Standard shipping', 149 | 'price': {'value': '3.95', 'currency': 'GBP'} 150 | }], 151 | #'shippingWeight': {'value': '200', 'unit': 'grams'} 152 | } 153 | 154 | gprod.google_shopping_id = offer_id 155 | gprod.save() 156 | 157 | return product_data 158 | 159 | def insertProduct(self,product): 160 | #TODO: Is this even necessary? I mean, who wants to upload just a single product... 161 | try: 162 | 163 | # Dictify the product 164 | product_data = self.buildProduct(product) 165 | 166 | # Add product. 167 | request = self.service.products().insert(merchantId=self.merchant_id, 168 | body=product_data) 169 | 170 | #Override the URI being set as it's junk! 171 | #request.uri = "https://www.googleapis.com/content/v2/" + self.merchant_id + "/products?alt=json" 172 | 173 | result = request.execute() 174 | print ('Product with offerId "%s" and title "%s" was created.' % 175 | (result['offerId'], result['title'])) 176 | 177 | except client.AccessTokenRefreshError: 178 | warn_exp_token() 179 | 180 | 181 | def batchInsertProducts(self,product_qset): 182 | GoogleProduct = get_model('gmerchant','GoogleProduct') 183 | 184 | def product_inserted(unused_request_id, response, exception): 185 | if exception is not None: 186 | # Do something with the exception. 187 | print 'There was an error: ' + str(exception) 188 | else: 189 | offer_id = smart_text(response['offerId'].encode('ascii', 'ignore')) 190 | 191 | gp = GoogleProduct.objects.get(google_shopping_id=offer_id) 192 | if not gp.google_shopping_created: 193 | gp.google_shopping_created = datetime.now() 194 | else: 195 | gp.google_shopping_updated = datetime.now() 196 | gp.save() 197 | 198 | print ('Product with offerId "%s" and title "%s" was created.' % 199 | (offer_id, smart_text(response['title'].encode('ascii', 'ignore')))) 200 | 201 | 202 | for block in chunks(product_qset,BATCH_SIZE): 203 | #Build a new batch request for this block of products 204 | batch = BatchHttpRequest(callback=product_inserted) 205 | for i in block: 206 | product = self.buildProduct(i) 207 | # Add product to the batch. 208 | batch.add(self.service.products().insert(merchantId=self.merchant_id, 209 | body=product)) 210 | try: 211 | #Let's send this batch off to the Goog. 212 | batch.execute() 213 | except client.AccessTokenRefreshError: 214 | warn_exp_token() 215 | 216 | def listProducts(self): 217 | try: 218 | request = self.service.products().list(merchantId=self.merchant_id, 219 | maxResults=MAX_PAGE_SIZE) 220 | print request.uri 221 | 222 | while request is not None: 223 | result = request.execute() 224 | if 'resources' in result: 225 | products = result['resources'] 226 | for product in products: 227 | print ('Product "%s" with title "%s" was found.' % 228 | (smart_text(product['id'].encode('ascii', 'ignore')), smart_text(product['title'].encode('ascii', 'ignore')))) 229 | 230 | request = self.service.products().list_next(request, result) 231 | else: 232 | print 'No products were found.' 233 | break 234 | 235 | except client.AccessTokenRefreshError: 236 | warn_exp_token() 237 | -------------------------------------------------------------------------------- /gmerchant/migrations/0007_auto__add_field_googleproduct_google_shopping_updated.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'GoogleProduct.google_shopping_updated' 12 | db.add_column(u'gmerchant_googleproduct', 'google_shopping_updated', 13 | self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), 14 | keep_default=False) 15 | 16 | 17 | def backwards(self, orm): 18 | # Deleting field 'GoogleProduct.google_shopping_updated' 19 | db.delete_column(u'gmerchant_googleproduct', 'google_shopping_updated') 20 | 21 | 22 | models = { 23 | 'catalogue.attributeoption': { 24 | 'Meta': {'object_name': 'AttributeOption'}, 25 | 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': "orm['catalogue.AttributeOptionGroup']"}), 26 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 27 | 'option': ('django.db.models.fields.CharField', [], {'max_length': '255'}) 28 | }, 29 | 'catalogue.attributeoptiongroup': { 30 | 'Meta': {'object_name': 'AttributeOptionGroup'}, 31 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 32 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}) 33 | }, 34 | 'catalogue.category': { 35 | 'Meta': {'ordering': "['full_name']", 'object_name': 'Category'}, 36 | 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), 37 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 38 | 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), 39 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 40 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 41 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), 42 | 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), 43 | 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), 44 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}) 45 | }, 46 | 'catalogue.option': { 47 | 'Meta': {'object_name': 'Option'}, 48 | 'code': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}), 49 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 50 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 51 | 'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'}) 52 | }, 53 | 'catalogue.product': { 54 | 'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'}, 55 | 'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.ProductAttribute']", 'through': "orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}), 56 | 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Category']", 'through': "orm['catalogue.ProductCategory']", 'symmetrical': 'False'}), 57 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 58 | 'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), 59 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 60 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 61 | 'is_discountable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 62 | 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Product']"}), 63 | 'product_class': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'products'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': "orm['catalogue.ProductClass']"}), 64 | 'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}), 65 | 'rating': ('django.db.models.fields.FloatField', [], {'null': 'True'}), 66 | 'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Product']", 'symmetrical': 'False', 'through': "orm['catalogue.ProductRecommendation']", 'blank': 'True'}), 67 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}), 68 | 'structure': ('django.db.models.fields.CharField', [], {'default': "'standalone'", 'max_length': '10'}), 69 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 70 | 'upc': ('oscar.models.fields.NullCharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) 71 | }, 72 | 'catalogue.productattribute': { 73 | 'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'}, 74 | 'code': ('django.db.models.fields.SlugField', [], {'max_length': '128'}), 75 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 76 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 77 | 'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}), 78 | 'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': "orm['catalogue.ProductClass']"}), 79 | 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 80 | 'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'}) 81 | }, 82 | 'catalogue.productattributevalue': { 83 | 'Meta': {'unique_together': "(('attribute', 'product'),)", 'object_name': 'ProductAttributeValue'}, 84 | 'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductAttribute']"}), 85 | 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 86 | 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 87 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 88 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_values'", 'to': "orm['catalogue.Product']"}), 89 | 'value_boolean': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), 90 | 'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), 91 | 'value_file': ('django.db.models.fields.files.FileField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 92 | 'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 93 | 'value_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 94 | 'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 95 | 'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}), 96 | 'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 97 | 'value_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) 98 | }, 99 | 'catalogue.productcategory': { 100 | 'Meta': {'ordering': "['product', 'category']", 'unique_together': "(('product', 'category'),)", 'object_name': 'ProductCategory'}, 101 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Category']"}), 102 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 103 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}) 104 | }, 105 | 'catalogue.productclass': { 106 | 'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'}, 107 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 108 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 109 | 'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}), 110 | 'requires_shipping': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 111 | 'slug': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}), 112 | 'track_stock': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) 113 | }, 114 | 'catalogue.productrecommendation': { 115 | 'Meta': {'ordering': "['primary', '-ranking']", 'unique_together': "(('primary', 'recommendation'),)", 'object_name': 'ProductRecommendation'}, 116 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 117 | 'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': "orm['catalogue.Product']"}), 118 | 'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}), 119 | 'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}) 120 | }, 121 | u'contenttypes.contenttype': { 122 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 123 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 124 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 125 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 126 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 127 | }, 128 | u'gmerchant.apiservicecredentials': { 129 | 'Meta': {'object_name': 'APIServiceCredentials'}, 130 | 'application_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 131 | 'client_email': ('django.db.models.fields.EmailField', [], {'max_length': '128', 'blank': 'True'}), 132 | 'client_id': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 133 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 134 | 'private_key_ciphertext': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 135 | 'private_key_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 136 | 'private_key_phrase': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}) 137 | }, 138 | u'gmerchant.googlecategory': { 139 | 'Meta': {'object_name': 'GoogleCategory'}, 140 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 141 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), 142 | 'source_idx': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) 143 | }, 144 | u'gmerchant.googlemerchantaccount': { 145 | 'Meta': {'object_name': 'GoogleMerchantAccount'}, 146 | 'account_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 147 | 'account_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 148 | 'catalogue_imported': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 149 | 'credentials': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.APIServiceCredentials']", 'null': 'True', 'blank': 'True'}), 150 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 151 | }, 152 | u'gmerchant.googleproduct': { 153 | 'Meta': {'object_name': 'GoogleProduct'}, 154 | 'google_shopping_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 155 | 'google_shopping_description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 156 | 'google_shopping_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), 157 | 'google_shopping_updated': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 158 | 'google_taxonomy': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.GoogleCategory']", 'null': 'True', 'blank': 'True'}), 159 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 160 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}), 161 | 'product_upc': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '32', 'null': 'True', 'blank': 'True'}), 162 | 'publish_google_shopping': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 163 | } 164 | } 165 | 166 | complete_apps = ['gmerchant'] -------------------------------------------------------------------------------- /gmerchant/migrations/0006_auto__add_field_googleproduct_publish_google_shopping__add_field_googl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'GoogleProduct.publish_google_shopping' 12 | db.add_column(u'gmerchant_googleproduct', 'publish_google_shopping', 13 | self.gf('django.db.models.fields.BooleanField')(default=False), 14 | keep_default=False) 15 | 16 | # Adding field 'GoogleProduct.google_taxonomy' 17 | db.add_column(u'gmerchant_googleproduct', 'google_taxonomy', 18 | self.gf('django.db.models.fields.related.ForeignKey')(to=orm['gmerchant.GoogleCategory'], null=True, blank=True), 19 | keep_default=False) 20 | 21 | # Adding field 'GoogleProduct.google_shopping_description' 22 | db.add_column(u'gmerchant_googleproduct', 'google_shopping_description', 23 | self.gf('django.db.models.fields.TextField')(null=True, blank=True), 24 | keep_default=False) 25 | 26 | 27 | def backwards(self, orm): 28 | # Deleting field 'GoogleProduct.publish_google_shopping' 29 | db.delete_column(u'gmerchant_googleproduct', 'publish_google_shopping') 30 | 31 | # Deleting field 'GoogleProduct.google_taxonomy' 32 | db.delete_column(u'gmerchant_googleproduct', 'google_taxonomy_id') 33 | 34 | # Deleting field 'GoogleProduct.google_shopping_description' 35 | db.delete_column(u'gmerchant_googleproduct', 'google_shopping_description') 36 | 37 | 38 | models = { 39 | 'catalogue.attributeoption': { 40 | 'Meta': {'object_name': 'AttributeOption'}, 41 | 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': "orm['catalogue.AttributeOptionGroup']"}), 42 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 43 | 'option': ('django.db.models.fields.CharField', [], {'max_length': '255'}) 44 | }, 45 | 'catalogue.attributeoptiongroup': { 46 | 'Meta': {'object_name': 'AttributeOptionGroup'}, 47 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 48 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}) 49 | }, 50 | 'catalogue.category': { 51 | 'Meta': {'ordering': "['full_name']", 'object_name': 'Category'}, 52 | 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), 53 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 54 | 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), 55 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 56 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 57 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), 58 | 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), 59 | 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), 60 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}) 61 | }, 62 | 'catalogue.option': { 63 | 'Meta': {'object_name': 'Option'}, 64 | 'code': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}), 65 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 67 | 'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'}) 68 | }, 69 | 'catalogue.product': { 70 | 'Meta': {'ordering': "['-date_created']", 'object_name': 'Product'}, 71 | 'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.ProductAttribute']", 'through': "orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}), 72 | 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Category']", 'through': "orm['catalogue.ProductCategory']", 'symmetrical': 'False'}), 73 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 74 | 'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), 75 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 76 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 77 | 'is_discountable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 78 | 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['catalogue.Product']"}), 79 | 'product_class': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'products'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': "orm['catalogue.ProductClass']"}), 80 | 'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}), 81 | 'rating': ('django.db.models.fields.FloatField', [], {'null': 'True'}), 82 | 'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Product']", 'symmetrical': 'False', 'through': "orm['catalogue.ProductRecommendation']", 'blank': 'True'}), 83 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}), 84 | 'structure': ('django.db.models.fields.CharField', [], {'default': "'standalone'", 'max_length': '10'}), 85 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 86 | 'upc': ('oscar.models.fields.NullCharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) 87 | }, 88 | 'catalogue.productattribute': { 89 | 'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'}, 90 | 'code': ('django.db.models.fields.SlugField', [], {'max_length': '128'}), 91 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 92 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 93 | 'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}), 94 | 'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': "orm['catalogue.ProductClass']"}), 95 | 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 96 | 'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'}) 97 | }, 98 | 'catalogue.productattributevalue': { 99 | 'Meta': {'unique_together': "(('attribute', 'product'),)", 'object_name': 'ProductAttributeValue'}, 100 | 'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.ProductAttribute']"}), 101 | 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}), 102 | 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), 103 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 104 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_values'", 'to': "orm['catalogue.Product']"}), 105 | 'value_boolean': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), 106 | 'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), 107 | 'value_file': ('django.db.models.fields.files.FileField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 108 | 'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 109 | 'value_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 110 | 'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 111 | 'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}), 112 | 'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 113 | 'value_text': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) 114 | }, 115 | 'catalogue.productcategory': { 116 | 'Meta': {'ordering': "['product', 'category']", 'unique_together': "(('product', 'category'),)", 'object_name': 'ProductCategory'}, 117 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Category']"}), 118 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 119 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}) 120 | }, 121 | 'catalogue.productclass': { 122 | 'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'}, 123 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 124 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 125 | 'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}), 126 | 'requires_shipping': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 127 | 'slug': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}), 128 | 'track_stock': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) 129 | }, 130 | 'catalogue.productrecommendation': { 131 | 'Meta': {'ordering': "['primary', '-ranking']", 'unique_together': "(('primary', 'recommendation'),)", 'object_name': 'ProductRecommendation'}, 132 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 133 | 'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': "orm['catalogue.Product']"}), 134 | 'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}), 135 | 'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}) 136 | }, 137 | u'contenttypes.contenttype': { 138 | 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 139 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 140 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 141 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 142 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 143 | }, 144 | u'gmerchant.apiservicecredentials': { 145 | 'Meta': {'object_name': 'APIServiceCredentials'}, 146 | 'application_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 147 | 'client_email': ('django.db.models.fields.EmailField', [], {'max_length': '128', 'blank': 'True'}), 148 | 'client_id': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 149 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 150 | 'private_key_ciphertext': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 151 | 'private_key_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 152 | 'private_key_phrase': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}) 153 | }, 154 | u'gmerchant.googlecategory': { 155 | 'Meta': {'object_name': 'GoogleCategory'}, 156 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 157 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), 158 | 'source_idx': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) 159 | }, 160 | u'gmerchant.googlemerchantaccount': { 161 | 'Meta': {'object_name': 'GoogleMerchantAccount'}, 162 | 'account_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 163 | 'account_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 164 | 'catalogue_imported': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 165 | 'credentials': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.APIServiceCredentials']", 'null': 'True', 'blank': 'True'}), 166 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 167 | }, 168 | u'gmerchant.googleproduct': { 169 | 'Meta': {'object_name': 'GoogleProduct'}, 170 | 'google_shopping_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 171 | 'google_shopping_description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 172 | 'google_shopping_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), 173 | 'google_taxonomy': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.GoogleCategory']", 'null': 'True', 'blank': 'True'}), 174 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 175 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['catalogue.Product']"}), 176 | 'product_upc': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '32', 'null': 'True', 'blank': 'True'}), 177 | 'publish_google_shopping': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) 178 | } 179 | } 180 | 181 | complete_apps = ['gmerchant'] -------------------------------------------------------------------------------- /gmerchant/migrations/0004_add_model_GoogleProduct.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'GoogleProduct' 12 | db.create_table(u'gmerchant_googleproduct', ( 13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('product', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['catalogue.Product'])), 15 | ('google_shopping_id', self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True)), 16 | ('google_shopping_created', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), 17 | )) 18 | db.send_create_signal(u'gmerchant', ['GoogleProduct']) 19 | 20 | 21 | def backwards(self, orm): 22 | # Deleting model 'GoogleProduct' 23 | db.delete_table(u'gmerchant_googleproduct') 24 | 25 | 26 | models = { 27 | u'catalogue.attributeentity': { 28 | 'Meta': {'object_name': 'AttributeEntity'}, 29 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 30 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 31 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'}), 32 | 'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': u"orm['catalogue.AttributeEntityType']"}) 33 | }, 34 | u'catalogue.attributeentitytype': { 35 | 'Meta': {'object_name': 'AttributeEntityType'}, 36 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 37 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 38 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'}) 39 | }, 40 | u'catalogue.attributeoption': { 41 | 'Meta': {'object_name': 'AttributeOption'}, 42 | 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': u"orm['catalogue.AttributeOptionGroup']"}), 43 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 44 | 'option': ('django.db.models.fields.CharField', [], {'max_length': '255'}) 45 | }, 46 | u'catalogue.attributeoptiongroup': { 47 | 'Meta': {'object_name': 'AttributeOptionGroup'}, 48 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 49 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}) 50 | }, 51 | u'catalogue.category': { 52 | 'Meta': {'ordering': "['full_name']", 'object_name': 'Category'}, 53 | 'cover_product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), 54 | 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), 55 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 56 | 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), 57 | 'icon': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['iconlib.Icon']", 'null': 'True', 'blank': 'True'}), 58 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 59 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 60 | 'is_gift_category': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 61 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), 62 | 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), 63 | 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), 64 | 'short_description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 65 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}), 66 | 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) 67 | }, 68 | u'catalogue.option': { 69 | 'Meta': {'object_name': 'Option'}, 70 | 'code': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}), 71 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 72 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 73 | 'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'}) 74 | }, 75 | u'catalogue.product': { 76 | 'Meta': {'ordering': "['priority', 'title']", 'object_name': 'Product'}, 77 | 'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.ProductAttribute']", 'through': u"orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}), 78 | 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Category']", 'through': u"orm['catalogue.ProductCategory']", 'symmetrical': 'False'}), 79 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 80 | 'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), 81 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 82 | 'featured_product': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 83 | 'google_shopping_description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 84 | 'google_taxonomy': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.GoogleCategory']", 'null': 'True', 'blank': 'True'}), 85 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 86 | 'ingredients': ('django.db.models.fields.TextField', [], {'max_length': '1000', 'null': 'True', 'blank': 'True'}), 87 | 'is_discountable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 88 | 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': u"orm['catalogue.Product']"}), 89 | 'priority': ('django.db.models.fields.PositiveIntegerField', [], {'default': '99'}), 90 | 'product_class': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'products'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['catalogue.ProductClass']"}), 91 | 'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}), 92 | 'publish_google_shopping': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 93 | 'rating': ('django.db.models.fields.FloatField', [], {'null': 'True'}), 94 | 'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Product']", 'symmetrical': 'False', 'through': u"orm['catalogue.ProductRecommendation']", 'blank': 'True'}), 95 | 'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': u"orm['catalogue.Product']"}), 96 | 'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}), 97 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}), 98 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 99 | 'upc': ('oscar.models.fields.NullCharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) 100 | }, 101 | u'catalogue.productattribute': { 102 | 'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'}, 103 | 'code': ('django.db.models.fields.SlugField', [], {'max_length': '128'}), 104 | 'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}), 105 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 106 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 107 | 'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}), 108 | 'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': u"orm['catalogue.ProductClass']"}), 109 | 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 110 | 'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'}) 111 | }, 112 | u'catalogue.productattributevalue': { 113 | 'Meta': {'object_name': 'ProductAttributeValue'}, 114 | 'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.ProductAttribute']"}), 115 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 116 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_values'", 'to': u"orm['catalogue.Product']"}), 117 | 'value_boolean': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), 118 | 'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), 119 | 'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}), 120 | 'value_file': ('django.db.models.fields.files.FileField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 121 | 'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 122 | 'value_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 123 | 'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 124 | 'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}), 125 | 'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 126 | 'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) 127 | }, 128 | u'catalogue.productcategory': { 129 | 'Meta': {'ordering': "['product', 'category']", 'object_name': 'ProductCategory'}, 130 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Category']"}), 131 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 132 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"}) 133 | }, 134 | u'catalogue.productclass': { 135 | 'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'}, 136 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 137 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 138 | 'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}), 139 | 'requires_shipping': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 140 | 'slug': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}), 141 | 'track_stock': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) 142 | }, 143 | u'catalogue.productrecommendation': { 144 | 'Meta': {'object_name': 'ProductRecommendation'}, 145 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 146 | 'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': u"orm['catalogue.Product']"}), 147 | 'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}), 148 | 'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"}) 149 | }, 150 | u'gmerchant.apiservicecredentials': { 151 | 'Meta': {'object_name': 'APIServiceCredentials'}, 152 | 'application_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 153 | 'client_email': ('django.db.models.fields.EmailField', [], {'max_length': '128', 'blank': 'True'}), 154 | 'client_id': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 155 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 156 | 'private_key_ciphertext': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 157 | 'private_key_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 158 | 'private_key_phrase': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}) 159 | }, 160 | u'gmerchant.googlecategory': { 161 | 'Meta': {'object_name': 'GoogleCategory'}, 162 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 163 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), 164 | 'source_idx': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) 165 | }, 166 | u'gmerchant.googlemerchantaccount': { 167 | 'Meta': {'object_name': 'GoogleMerchantAccount'}, 168 | 'account_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 169 | 'account_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 170 | 'catalogue_imported': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 171 | 'credentials': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.APIServiceCredentials']", 'null': 'True', 'blank': 'True'}), 172 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 173 | }, 174 | u'gmerchant.googleproduct': { 175 | 'Meta': {'object_name': 'GoogleProduct'}, 176 | 'google_shopping_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 177 | 'google_shopping_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), 178 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 179 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"}) 180 | }, 181 | u'iconlib.icon': { 182 | 'Meta': {'object_name': 'Icon'}, 183 | 'has_svg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 184 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 185 | 'image': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), 186 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}), 187 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}) 188 | } 189 | } 190 | 191 | complete_apps = ['gmerchant'] -------------------------------------------------------------------------------- /gmerchant/migrations/0005_auto__add_field_googleproduct_product_upc__add_index_googleproduct_goo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding field 'GoogleProduct.product_upc' 12 | db.add_column(u'gmerchant_googleproduct', 'product_upc', 13 | self.gf('django.db.models.fields.CharField')(db_index=True, max_length=32, null=True, blank=True), 14 | keep_default=False) 15 | 16 | # Adding index on 'GoogleProduct', fields ['google_shopping_id'] 17 | db.create_index(u'gmerchant_googleproduct', ['google_shopping_id']) 18 | 19 | 20 | def backwards(self, orm): 21 | # Removing index on 'GoogleProduct', fields ['google_shopping_id'] 22 | db.delete_index(u'gmerchant_googleproduct', ['google_shopping_id']) 23 | 24 | # Deleting field 'GoogleProduct.product_upc' 25 | db.delete_column(u'gmerchant_googleproduct', 'product_upc') 26 | 27 | 28 | models = { 29 | u'catalogue.attributeentity': { 30 | 'Meta': {'object_name': 'AttributeEntity'}, 31 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 32 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 33 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'}), 34 | 'type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'entities'", 'to': u"orm['catalogue.AttributeEntityType']"}) 35 | }, 36 | u'catalogue.attributeentitytype': { 37 | 'Meta': {'object_name': 'AttributeEntityType'}, 38 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 39 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 40 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'blank': 'True'}) 41 | }, 42 | u'catalogue.attributeoption': { 43 | 'Meta': {'object_name': 'AttributeOption'}, 44 | 'group': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'options'", 'to': u"orm['catalogue.AttributeOptionGroup']"}), 45 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 46 | 'option': ('django.db.models.fields.CharField', [], {'max_length': '255'}) 47 | }, 48 | u'catalogue.attributeoptiongroup': { 49 | 'Meta': {'object_name': 'AttributeOptionGroup'}, 50 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 51 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}) 52 | }, 53 | u'catalogue.category': { 54 | 'Meta': {'ordering': "['full_name']", 'object_name': 'Category'}, 55 | 'cover_product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), 56 | 'depth': ('django.db.models.fields.PositiveIntegerField', [], {}), 57 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 58 | 'full_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), 59 | 'icon': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['iconlib.Icon']", 'null': 'True', 'blank': 'True'}), 60 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 61 | 'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 62 | 'is_gift_category': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 63 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), 64 | 'numchild': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), 65 | 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), 66 | 'short_description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 67 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}), 68 | 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) 69 | }, 70 | u'catalogue.option': { 71 | 'Meta': {'object_name': 'Option'}, 72 | 'code': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}), 73 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 74 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 75 | 'type': ('django.db.models.fields.CharField', [], {'default': "'Required'", 'max_length': '128'}) 76 | }, 77 | u'catalogue.product': { 78 | 'Meta': {'ordering': "['priority', 'title']", 'object_name': 'Product'}, 79 | 'attributes': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.ProductAttribute']", 'through': u"orm['catalogue.ProductAttributeValue']", 'symmetrical': 'False'}), 80 | 'categories': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Category']", 'through': u"orm['catalogue.ProductCategory']", 'symmetrical': 'False'}), 81 | 'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 82 | 'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}), 83 | 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 84 | 'featured_product': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 85 | 'google_shopping_description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 86 | 'google_taxonomy': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.GoogleCategory']", 'null': 'True', 'blank': 'True'}), 87 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 88 | 'ingredients': ('django.db.models.fields.TextField', [], {'max_length': '1000', 'null': 'True', 'blank': 'True'}), 89 | 'is_discountable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 90 | 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'variants'", 'null': 'True', 'to': u"orm['catalogue.Product']"}), 91 | 'priority': ('django.db.models.fields.PositiveIntegerField', [], {'default': '99'}), 92 | 'product_class': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'products'", 'null': 'True', 'on_delete': 'models.PROTECT', 'to': u"orm['catalogue.ProductClass']"}), 93 | 'product_options': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}), 94 | 'publish_google_shopping': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 95 | 'rating': ('django.db.models.fields.FloatField', [], {'null': 'True'}), 96 | 'recommended_products': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Product']", 'symmetrical': 'False', 'through': u"orm['catalogue.ProductRecommendation']", 'blank': 'True'}), 97 | 'related_products': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'relations'", 'blank': 'True', 'to': u"orm['catalogue.Product']"}), 98 | 'score': ('django.db.models.fields.FloatField', [], {'default': '0.0', 'db_index': 'True'}), 99 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255'}), 100 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 101 | 'upc': ('oscar.models.fields.NullCharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}) 102 | }, 103 | u'catalogue.productattribute': { 104 | 'Meta': {'ordering': "['code']", 'object_name': 'ProductAttribute'}, 105 | 'code': ('django.db.models.fields.SlugField', [], {'max_length': '128'}), 106 | 'entity_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeEntityType']", 'null': 'True', 'blank': 'True'}), 107 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 108 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 109 | 'option_group': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeOptionGroup']", 'null': 'True', 'blank': 'True'}), 110 | 'product_class': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attributes'", 'null': 'True', 'to': u"orm['catalogue.ProductClass']"}), 111 | 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 112 | 'type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '20'}) 113 | }, 114 | u'catalogue.productattributevalue': { 115 | 'Meta': {'object_name': 'ProductAttributeValue'}, 116 | 'attribute': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.ProductAttribute']"}), 117 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 118 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_values'", 'to': u"orm['catalogue.Product']"}), 119 | 'value_boolean': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}), 120 | 'value_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), 121 | 'value_entity': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeEntity']", 'null': 'True', 'blank': 'True'}), 122 | 'value_file': ('django.db.models.fields.files.FileField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 123 | 'value_float': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 124 | 'value_image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 125 | 'value_integer': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 126 | 'value_option': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.AttributeOption']", 'null': 'True', 'blank': 'True'}), 127 | 'value_richtext': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 128 | 'value_text': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}) 129 | }, 130 | u'catalogue.productcategory': { 131 | 'Meta': {'ordering': "['product', 'category']", 'object_name': 'ProductCategory'}, 132 | 'category': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Category']"}), 133 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 134 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"}) 135 | }, 136 | u'catalogue.productclass': { 137 | 'Meta': {'ordering': "['name']", 'object_name': 'ProductClass'}, 138 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 139 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 140 | 'options': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['catalogue.Option']", 'symmetrical': 'False', 'blank': 'True'}), 141 | 'requires_shipping': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 142 | 'slug': ('oscar.models.fields.autoslugfield.AutoSlugField', [], {'allow_duplicates': 'False', 'max_length': '128', 'separator': "u'-'", 'blank': 'True', 'unique': 'True', 'populate_from': "'name'", 'overwrite': 'False'}), 143 | 'track_stock': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) 144 | }, 145 | u'catalogue.productrecommendation': { 146 | 'Meta': {'object_name': 'ProductRecommendation'}, 147 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 148 | 'primary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'primary_recommendations'", 'to': u"orm['catalogue.Product']"}), 149 | 'ranking': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}), 150 | 'recommendation': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"}) 151 | }, 152 | u'gmerchant.apiservicecredentials': { 153 | 'Meta': {'object_name': 'APIServiceCredentials'}, 154 | 'application_name': ('django.db.models.fields.CharField', [], {'max_length': '256'}), 155 | 'client_email': ('django.db.models.fields.EmailField', [], {'max_length': '128', 'blank': 'True'}), 156 | 'client_id': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 157 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 158 | 'private_key_ciphertext': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 159 | 'private_key_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 160 | 'private_key_phrase': ('django.db.models.fields.CharField', [], {'max_length': '512', 'blank': 'True'}) 161 | }, 162 | u'gmerchant.googlecategory': { 163 | 'Meta': {'object_name': 'GoogleCategory'}, 164 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 165 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}), 166 | 'source_idx': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) 167 | }, 168 | u'gmerchant.googlemerchantaccount': { 169 | 'Meta': {'object_name': 'GoogleMerchantAccount'}, 170 | 'account_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 171 | 'account_name': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 172 | 'catalogue_imported': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 173 | 'credentials': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['gmerchant.APIServiceCredentials']", 'null': 'True', 'blank': 'True'}), 174 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) 175 | }, 176 | u'gmerchant.googleproduct': { 177 | 'Meta': {'object_name': 'GoogleProduct'}, 178 | 'google_shopping_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 179 | 'google_shopping_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}), 180 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 181 | 'product': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['catalogue.Product']"}), 182 | 'product_upc': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '32', 'null': 'True', 'blank': 'True'}) 183 | }, 184 | u'iconlib.icon': { 185 | 'Meta': {'object_name': 'Icon'}, 186 | 'has_svg': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 187 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 188 | 'image': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), 189 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}), 190 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}) 191 | } 192 | } 193 | 194 | complete_apps = ['gmerchant'] --------------------------------------------------------------------------------