class Song(BaseModel): name = db.StringField() comment_count = db.IntField() comments = db.ListField(db.ReferenceField('Comment')) artist = db.ReferenceField('Artist') meta = {'indexes': ['name']} @property def url(self): return SONG_URL.format(self.id) @property def artist_url(self): return self.artist.url
class Artist(BaseModel): name = db.StringField() picture = db.StringField() songs = db.ListField(db.ReferenceField('Song')) meta = { # 'indexes': ['name', '$name'] # 不支持text search? 'indexes': ['name'] } @property def url(self): return ARTIST_URL.format(self.id)
class User(db.Document, UserMixin): email = db.StringField(max_length=255, unique=True) password = db.StringField(max_length=255, required=True) name = db.StringField(max_length=255) active = db.BooleanField(default=True) roles = db.ListField(db.ReferenceField(Role), default=[]) registered_at = db.DateTimeField() def __unicode__(self): return 'User %s' % self.email @property def is_admin(self): return self.has_role('admin') def save(self, *args, **kwargs): self.registered_at = datetime.now() super(User, self).save(*args, **kwargs)
class ApishopCategory(TempDBMixin, GetOrCreateMixin, db.Document): id = db.IntField(primary_key=True, unique=True) parent_id = db.IntField() name = db.StringField(max_length=255) category = db.ReferenceField(RealCategory, default=None) meta = {'indexes': ['parent_id', 'category']} def __unicode__(self): return self.name @classmethod def get_full_tree(cls): def build_tree(categories, parent_id=0): branch = [] for index, category in enumerate(categories): if category.parent_id == parent_id: form = ApishopCategoryLinkForm(request.form, obj=category) branch.append( dict(id=category.id, name=category.name, offers=category.get_offers_count, form=form, childs=build_tree(categories, category.id))) return branch categories = cls.objects.order_by('+parent_id') return build_tree(categories) @property def get_offers_count(self): return ApishopOffer.objects(category_id=self.id).count() @classmethod def copy_offers(cls): # Для каждой категории со связью отправить товар в RealOffer # для дальнейшей обработки и сохранения. После копирования очистить # базу временных товаров. categories_with_link = cls.objects(category__ne=None) for category in categories_with_link: for offer in ApishopOffer.objects(category_id=category.id): RealOffer.populate(offer, category.category)
class Review(db.Document): offer = db.ReferenceField('Offer') fullname = db.StringField(max_length=200) email = db.StringField(max_length=200) text = db.StringField() rating = db.IntField(default=0) is_moderated = db.BooleanField(default=False) is_viewed = db.BooleanField(default=False) created_at = db.DateTimeField(default=datetime.now) meta = { 'ordering': ['-created_at'] } def toggle_moderate(self): self.update(set__is_moderated=not self.is_moderated) self.reload() def set_viewed(self): if not self.is_viewed: self.update(set__is_viewed=True) self.reload()
class Page(DispatcherMixin, PositionMixin, IntIDMixin, PathMixin, BreadcrumbsMixin, db.Document): name = db.StringField(max_length=255, verbose_name=u'Название', required=True) parent = db.ReferenceField('Page', default=None, verbose_name=u'Родитель') metas = db.EmbeddedDocumentField(Metas) content = db.StringField(verbose_name=u'Описание') extra = db.DictField(default={'type': 'None'}) meta = { 'ordering': ['+position'], 'indexes': ['path', 'parent', 'position'] } def __unicode__(self): return u'%s' % self.name def __repr__(self): return u'%s(%s)' % (self.__class__.__name__, self.id) @cached_property def get_title(self): if self.metas and self.metas.title: return self.metas.title return self.name def get_extra(self): return self.extra def save(self, *args, **kwargs): self.validate_position(kwargs.get('parent', self.parent)) super(Page, self).save(*args, **kwargs)
class Comment(BaseModel): content = db.StringField() like_count = db.IntField() user = db.ReferenceField('User') song = db.ReferenceField('Song') meta = {'indexes': ['-like_count']} @property def user_url(self): return self.user.url @property def artist_url(self): return self.song.artist_url @classmethod def cache_by_key(cls, key, ids): """给内部用""" cache.delete(key) cache.rpush(key, *ids) cache.expire(key, TIMEOUT) @classmethod def get_random_by_session_id(cls, session_id, start=0, limit=20): """给API用""" key = RANDOM_KEY.format(session_id=session_id) if not start % SAMPLE_SIZE: ids = cls.get_sample_ids(SAMPLE_SIZE) cls.cache_by_key(key, ids) else: ids = cache.lrange(key, start, start + limit) if not ids: ids = cls.get_sample_ids(SAMPLE_SIZE) cls.cache_by_key(key, ids) comments = cls.get_multi(ids) return comments @classmethod def order_by_star(cls, start=0, limit=20): """给API用""" ids = cache.lrange(STAR_KEY, start, start + limit) if not ids: ids = [ c.id for c in cls.objects.order_by('-like_count')[:TOTAL_SIZE] ] # noqa cache.delete(STAR_KEY) cache.rpush(STAR_KEY, *ids) ids = ids[start:start + limit] return cls.get_multi(ids) def to_dict(self): song_obj = self.song user_obj = self.user artist_obj = song_obj.artist song = {'id': song_obj.id, 'url': song_obj.url, 'name': song_obj.name} artist = { 'id': artist_obj.id, 'avatar': artist_obj.picture, 'name': artist_obj.name, 'url': artist_obj.url } user = { 'avatar': user_obj.picture, 'name': user_obj.name, 'url': user_obj.url } return { 'song': song, 'user': user, 'artist': artist, 'content': self.content }
class Comment(BaseModel): content = db.StringField() like_count = db.IntField() user = db.ReferenceField('User') movie = db.ReferenceField('Movie', reverse_delete_rule=db.CASCADE) meta = {'indexes': ['-like_count']} @property def url(self): return COMMENT_URL.format(self.id) @property def user_url(self): return self.user.url @property def movie_url(self): return self.movie.url @classmethod def cache_by_key(cls, key, ids): cache.delete(key) cache.rpush(key, *ids) cache.expire(key, TIMEOUT) @classmethod def order_by_star(cls, start=0, limit=20): ids = cache.lrange(START_KEY, start, start + limit) if not ids: ids = [ c.id for c in cls.objects.order_by('-like_count')[:TOTAL_SIZE] ] cache.delete(START_KEY) cache.rpush(START_KEY, *ids) ids = ids[start:start + limit] return cls.get_multi(ids) def to_dict(self): movie_obj = self.movie user_obj = self.user movie = { 'id': movie_obj.id, 'name': movie_obj.name, 'mark': movie_obj.mark, 'url': movie_obj.url, 'picture': movie_obj.picture } user = { 'id': user_obj.id, 'avatar': user_obj.picture, 'name': user_obj.name, } return { 'movie': movie, 'user': user, 'content': self.content, 'like_count': self.like_count }
class OrderOffer(db.EmbeddedDocument): offer = db.ReferenceField('Offer') price = db.FloatField(default=0.0) oldprice = db.FloatField(default=0.0) quantity = db.IntField(default=1) variant = db.StringField(default=None)
class CartOffer(db.EmbeddedDocument): offer = db.ReferenceField('Offer') quantity = db.IntField(default=1) variant = db.StringField(default=None)
class Offer(DispatcherMixin, IntIDMixin, PathMixin, BreadcrumbsMixin, db.Document): name = db.StringField(max_length=255, verbose_name=u'Название', required=True) model = db.StringField(max_length=255, verbose_name=u'Модель') aid = db.IntField(verbose_name=u'ID apishops') articul = db.StringField(max_length=50, verbose_name=u'Артикул') available = db.BooleanField() price = db.EmbeddedDocumentField('OfferPrices', verbose_name=u'Цены') commissions = db.EmbeddedDocumentField('OfferPrices', verbose_name=u'Коммиссии') vendor = db.ReferenceField('Vendor', verbose_name=u'Производитель') parent = db.ReferenceField('Category', verbose_name=u'Категория') metas = db.EmbeddedDocumentField('Metas') stats = db.EmbeddedDocumentField('OfferStats') variants = db.ListField(db.EmbeddedDocumentField('OfferVariant')) pictures = db.ListField(db.EmbeddedDocumentField('OfferPicture')) special = db.ReferenceField('OfferSpecial') short_description = db.StringField(verbose_name=u'Короткое описание') description = db.StringField(verbose_name=u'Описание') canonical = db.ReferenceField('Offer', default=None, verbose_name=u'Каноникал') meta = { 'indexes': [{'fields': ['$name', "$description"], 'default_language': 'russian', 'weights': {'name': 10, 'description': 2} }, 'path', 'parent', 'articul', 'aid', 'price.ru', 'stats.popularity', ['available', 'price.ru'], ['available', 'stats.popularity']] } def __unicode__(self): return self.name def __repr__(self): return u'%s(%s)' % (self.__class__.__name__, self.id) @cache.memoize(60*60*24*7) def get_canonical(self): if not self.canonical or self.canonical == self: return None url_root = request.url_root return urlparse.urljoin(url_root, url_for('site.dispatcher', path=self.canonical.path)) @classmethod def populate(cls, copied_offer, category): offer = cls.objects(aid=copied_offer.id, articul=copied_offer.articul).first() if not offer: offer = cls.objects(aid=copied_offer.id).first() offer_info = deepcopy(copied_offer.prepare_to_copy) if offer: store_count = offer_info.get('stats').get('store_count', None) available = offer_info.get('available', None) price = offer_info.get('price', None) commissions = offer_info.get('commissions', None) variants = offer_info.get('variants', None) updates = {} price_change = False if copied_offer.articul != offer.articul: updates['set__articul'] = copied_offer.articul if store_count != offer.stats.store_count: updates['set__stats__store_count'] = store_count if available != offer.available: updates['set__available'] = available if price: for key in ('ru', 'by', 'kz'): if float(price.get(key)) != float(offer.get_price(key)): if key == 'ru': price_change = True updates['set__price__{}'.format(key)] = price.get(key) if commissions: for key in ('ru', 'by', 'kz'): if float(commissions.get(key)) != float(offer.get_commission(key)): updates['set__commissions__{}'.format(key)] = commissions.get(key) if variants: varts = [] for variant in variants: varts.append(OfferVariant(**variant)) updates['set__variants'] = varts if len(updates.keys()): if price_change: from modules.apishop.models import ApishopPriceChange if offer.special and offer.special.type == 'real': old_price = float(offer.special.prices.ru) else: old_price = float(offer.get_price('ru')) new_price = float(updates['set__price__ru']) if old_price != new_price: change = ApishopPriceChange(oid=offer.id, name=offer.name, old_price=old_price, new_price=new_price) change.save() updates['set__updated_at'] = datetime.now() offer.update(**updates) offer.reload() if price_change and offer.get_special is not None: current_special = offer.get_special new = current_special.create_from_self() new.populate_price(offer) new.save() offer.update(set__special=new) current_special.delete() else: vendor_name = offer_info.pop('vendor', None) if vendor_name: vendor = Vendor.get_or_create_by_name(vendor_name) else: vendor = None offer_info['vendor'] = vendor offer_info['parent'] = category offer = cls(**offer_info) offer.save() task = upload_offer_pictures.apply_async([offer]) @cache.memoize(60*60) def get_delivery_price(self, region_id=None): if not region_id: region_id = 53 region = Region.objects(id=region_id).first() if region: return dict(id=region.id, name=region.name, deliveries=[(d.method, d.price) for d in region.deliveries]) return None @staticmethod def sub_text(match): id = match.group('id') offer = Offer.objects(id=id).only('path').first() if not offer: return '' return url_for('site.dispatcher', path=offer.path) @property def get_description(self): comp = re.compile(r'%%\s*link_to_offer\s+(?P<id>[0-9]+)\s*%%', re.IGNORECASE) text = comp.sub(self.sub_text, self.description) return u'{}'.format(text) @property def is_in_favorites(self): favorites = session.get('favorites', []) return str(str(self.id) in favorites).lower() @property def generate_picture_name(self): uniq = str(uuid.uuid4())[:8] return '_'.join([self.slug, uniq]) def create_pictures_set(self, original): big = create_offer_image(original, quality=100) medium = create_offer_image(original, width=250, height=200, suffix='med') small = create_offer_image(original, width=60, height=60, suffix='sml') return dict(original=original, big=big, medium=medium, small=small) def get_pictures(self, for_download=False, typ=None): if for_download: return self.pictures if hasattr(self, 'pictures') else [] pictures = [] if self.pictures: for picture in self.pictures: if typ and getattr(picture, typ, None) is not None: url = url_for('media', filename=getattr(picture, typ)) elif not typ and getattr(picture, 'big', None) is not None: url = url_for('media', filename=getattr(picture, 'big')) else: url = url_for('media', filename=picture.original) if picture.original else picture.url pictures.append(url) else: if not typ: typ = 'big' pictures = [url_for('static', filename='img/nophoto_{}.svg'.format(typ))] return pictures @cached_property @cache.memoize(3600) def parent_cached(self): return (self.parent.name, self.parent.path) def get_variant(self, aid): if self.variants: try: aid = int(aid) except (TypeError, ValueError): pass for variant in self.variants: if variant.aid == aid: return variant else: return self.variants[0] return None def get_reviews(self): return Review.objects(offer=self.id, is_moderated=True) @cached_property def get_title(self): separator = current_app.config.get('DEFAULT_TITLE_SEPARATOR', ' | ') return separator.join([self.name, self.parent.get_title]) @property def is_in_stock(self): return self.available @property def get_special(self): return self.special or None @property def get_oldprice(self): if self.special: return smart_round(self.special.prices.ru) return None @property def get_timer(self): if self.special: return self.special.timer return None @classmethod def get_special_offers(cls): import random special_offers = cls.objects(special__ne=None).order_by('-stats.popularity') special_offers = [offer for offer in special_offers if offer.special.is_active] random.shuffle(special_offers) return special_offers @cache.memoize(3600) def get_picture(self, typ=None, absolute=False): pictures = self.get_pictures(typ=typ) if len(pictures): url = pictures[0] else: url = url_for('static', filename='img/nophoto.svg') if absolute: url = urlparse.urljoin(request.url_root, url) return url @cached_property def get_absolute_picture(self): pic = self.get_picture() if not pic: return None return ''.join([request.url_root.strip('/'), pic]) @cached_property def get_canonical_url(self): return ''.join([request.url_root.strip('/'), url_for('site.dispatcher', path=self.path)]) @cache.memoize(3600) def get_breadcrumbs(self): paths = self._split_path() breadcrumbs = [] objs = self.parent.__class__.objects(path__in=paths[:-1])\ .only('name', 'path')\ .order_by('path') for obj in objs: breadcrumbs.append((obj.name, obj.path)) return breadcrumbs def set_visit(self): cache.delete_memoized(self.get_visited) visited_offers = session.get('visited_offers', []) if self.id not in visited_offers: self.update(inc__stats__views=1) self.reload() visited_offers.insert(0, self.id) self.calculate_popularity() else: visited_offers.remove(self.id) visited_offers.insert(0, self.id) session['visited_offers'] = visited_offers def set_add_to_cart(self): added_to_cart = session.get('added_to_cart', []) if self.id not in added_to_cart: self.update(inc__stats__add_to_cart=1) self.reload() added_to_cart.append(self.id) self.calculate_popularity() self.reload() session['added_to_cart'] = added_to_cart def calculate_popularity(self): popularity = (self.stats.views * 1 + self.stats.add_to_cart * 2 + self.stats.orders * 3) / 3 self.update(set__stats__popularity=popularity) @classmethod @cache.memoize(60*5) def get_visited(cls): visited_offers = session.get('visited_offers', []) if len(visited_offers): visited = list(cls.objects(id__in=visited_offers[:15]) .only('id', 'name', 'price', 'pictures', 'path')) visited.sort(key=lambda k: visited_offers.index(k.id)) return visited return None @classmethod def get_popular(cls): return cls.objects(available=True).order_by('-stats.popularity') @cache.memoize(60*60) def get_random_ids(self, offer_id): max_items = 12 all_ids = sorted(Offer.objects(available=True, id__ne=offer_id).distinct('id')) length = len(all_ids) ids = [] if length: try: ids = random.sample(all_ids, max_items) except ValueError: ids = random.sample(all_ids, length) return ids def get_related(self): return self.__class__.objects(id__in=self.get_random_ids(self.id)) def remove_picture(self, idx): pictures = self.pictures picture = pictures.pop(idx) for typ in ('original', 'small', 'medium', 'big'): if hasattr(picture, typ): path = getattr(picture, typ, None) if path: delete_file_by_path(os.path.join(current_app.config['MEDIA_DIR'], path)) self.update(set__pictures=pictures) cache.delete_memoized(self.get_picture) def get_price(self, key='ru'): return smart_round(getattr(self.price, key)) def get_commission(self, key='ru'): return smart_round(getattr(self.commissions, key)) def save(self, *args, **kwargs): cache.delete_memoized(self.get_picture) cache.delete_memoized(self.get_visited) cache.delete_memoized(self.get_canonical) super(Offer, self).save(*args, **kwargs)
class Category(DispatcherMixin, PositionMixin, IntIDMixin, PathMixin, BreadcrumbsMixin, db.Document): name = db.StringField(max_length=255, verbose_name=u'Название') is_active = db.BooleanField(default=True, verbose_name=u'Включена') parent = db.ReferenceField('Category', default=None, verbose_name=u'Родитель') description = db.StringField(verbose_name=u'Описание') stats = db.EmbeddedDocumentField('CategoryStats') metas = db.EmbeddedDocumentField('Metas') meta = { 'ordering': ['+position'], 'indexes': [{'fields': ['$name', "$description"], 'default_language': 'russian', 'weights': {'name': 10, 'description': 2} }, 'path', 'parent', 'position'] } def __unicode__(self): return u'%s' % self.name def __repr__(self): return u'%s(%s)' % (self.__class__.__name__, self.id) def get_childrens(self): return self.__class__.objects(parent=self) def get_childs(self): return self.__class__.objects(__raw__={'path': {'$regex': '^{0}' .format(self.path)}}).order_by('path') def delete(self, *args, **kwargs): childrens = self.get_childrens() for child in childrens: child.delete() cache.delete_memoized(self.get_tree) super(Category, self).delete(*args, **kwargs) def save(self, *args, **kwargs): self.validate_position(kwargs.get('parent', self.parent)) cache.delete_memoized(self.get_tree) super(Category, self).save(*args, **kwargs) @cache.memoize(3600) def get_tree_from(self): paths = self._split_path() root_category = self.__class__.objects.get(path=paths[0]) tree = self.__class__.get_tree(parent=None, paths=paths) return tree def get_breadcrumbs(self): paths = self._split_path() breadcrumbs = [] for path in paths[:-1]: obj = self.__class__.objects.get(path=path) breadcrumbs.append((obj.name, obj.path)) return breadcrumbs @cache.memoize(60*60*24*7) def get_category_root_url(self): if not self.parent: return None root = self.__class__.objects(path=self.path.split('/')[0]).first() return urlparse.urljoin(request.url_root, url_for('site.dispatcher', path=root.path)) @cache.memoize(60*60*24*7) def get_root(self): if not self.parent: return self paths = self._split_path() root = self.__class__.objects(path=paths[0]).first() return root @cached_property def get_title(self): if self.metas.title: return self.metas.title names = [self.name] parent = self.parent while parent: names.append(parent.name) parent = parent.parent separator = current_app.config.get('DEFAULT_TITLE_SEPARATOR', ' | ') return separator.join(names) @property def get_offers_count(self): count = self.stats.items if self.stats else 0 return count @property def depth(self): return len(self.path.split('/')) - 1 @classmethod @cache.memoize(3600) def get_tree(cls, parent=None, paths=None): objects = cls.objects(parent=parent) branch = [] if not paths: for obj in objects: childs = obj.get_childs() if childs.count() > 1: branch.append([obj, obj.__class__.get_tree(parent=obj)]) else: branch.append(obj) else: for obj in objects: if obj.path in paths: childs = obj.get_childs() if childs.count() > 1: branch.append([obj, obj.__class__.get_tree(parent=obj, paths=paths)]) else: branch.append(obj) else: branch.append(obj) return branch def save(self, *args, **kwargs): cache.delete_memoized(self.__class__.get_tree) cache.delete_memoized(self.get_tree_from) cache.delete_memoized(self.get_category_root_url) cache.delete_memoized(self.get_root) super(Category, self).save(*args, **kwargs) @classmethod def post_save(cls, sender, document, **kwargs): childs = cls.objects(__raw__={'path': {'$regex': '^{0}' .format(document.old_path)}}).order_by('path') if len(childs) > 1 and document.old_path != document.path: for child in childs: child.save()