class ApishopOffer(TempDBMixin, GetOrCreateMixin, db.Document): id = db.IntField(primary_key=True) available = db.BooleanField() articul = db.StringField() price = db.DictField(default=dict(ru=None, by=None, kz=None)) commissions = db.DictField(default=dict(ru=None, by=None, kz=None)) category_id = db.IntField() name = db.StringField(max_length=255) model = db.StringField(max_length=255) vendor = db.StringField(max_length=100) pictures = db.ListField(db.StringField(max_length=255), default=[]) description = db.StringField() variants = db.ListField(db.DictField()) store_count = db.IntField() enabled_to_copy = db.BooleanField(default=True) meta = {'indexes': ['category_id']} def __unicode__(self): return '%s: %s' % (self.id, self.name) def get_picture(self): if len(self.pictures): return self.pictures[0] @property def prepare_to_copy(self): return dict(aid=self.id, articul=self.articul, available=self.available, price=self.price, commissions=self.commissions, name=self.name, model=self.model, vendor=self.vendor, pictures=[{ 'url': picture } for picture in self.pictures], description=self.description, stats=dict(store_count=self.store_count), variants=[{ 'store_count': variant.get('store_count'), 'aid': variant.get('id'), 'name': variant.get('name') } for variant in self.variants])
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 Process(BaseModel): STATUS = PENDING, SUCCEEDED, FAILED = range(3) status = db.IntField(choices=STATUS, default=PENDING) @property def is_success(self): return self.status == self.SUCCEEDED def make_succeed(self): return self.update(status=self.SUCCEEDED) def make_fail(self): return self.update(status=self.FAILED)
class DeliveryMethod(db.Document): id = db.IntField(primary_key=True) name = db.StringField(max_length=400) meta = { 'ordering': ['+id'], 'indexes': ['name'] } def __unicode__(self): return self.name def __str__(self): return u'%s(%s)' % (self.__class__.__name__, self.id)
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 PositionMixin(object): position = db.IntField() def validate_position(self, parent=None): if self.position is None: parent = getattr(self, 'parent', parent) filters = dict(parent=parent) if self.id: filters['id__ne'] = self.id last = self.__class__.objects(**filters).order_by('-position').first() if last: self.position = last.position + 1 else: self.position = 0
class IntIDMixin(object): id = db.IntField(primary_key=True) created_at = db.DateTimeField(default=datetime.now) updated_at = db.DateTimeField() def _generate_id(self): last_obj = self.__class__.objects.only('id').order_by('-id').first() self.id = last_obj.id + 1 if last_obj else 1 def save(self, *args, **kwargs): if not self.id: self._generate_id() if not self.created_at: self.created_at = datetime.now() self.updated_at = datetime.now() super(IntIDMixin, self).save(*args, **kwargs)
class ApishopConfig(TempDBMixin, db.Document): login = db.StringField(max_length=50, required=True) shop_id = db.IntField(required=True) yml_file = db.StringField(max_length=255) task = db.DictField(default={'id': None, 'name': None}) updated_at = db.DateTimeField() _password = db.StringField(required=True) def __unicode__(self): return self.login @classmethod def get_config(cls): return cls.objects.first() @property def password(self): return decode_string(self._password) @password.setter def password(self, value): self._password = encode_string(value) def set_task(self, task): self.update(set__task=dict(id=task.task_id, name=task.task_name)) def set_updated_at(self): self.update(set__updated_at=datetime.datetime.now()) @property def is_yml_exists(self): if self.yml_file: return check_file_exists(self.yml_file, relative=True) return False @property def task_is_ready(self): if self.task.get('id'): return celery.AsyncResult(self.task.get('id')).ready() return True def delete(self, *args, **kwargs): delete_file_by_path(self.yml_file, relative=True) ApishopCategory.objects.delete() ApishopOffer.objects.delete() super(ApishopConfig, self).delete(*args, **kwargs)
class ApishopPriceChange(TempDBMixin, db.Document): oid = db.IntField() name = db.StringField() old_price = db.FloatField() new_price = db.FloatField() date = db.DateTimeField(default=datetime.datetime.now) meta = {'ordering': ['-date']} @classmethod def create_or_update(cls, **kwargs): oid = kwargs.pop('oid') obj = cls.objects(oid=oid) if len(obj) >= 1: for o in obj: o.delete() obj = cls(**kwargs) obj.save() return obj
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 BaseModel(db.Document): id = db.IntField(primary_key=True) create_at = db.DateTimeField(default=datetime.now) update_at = db.DateTimeField() meta = {'allow_inheritance': True, 'abstract': True, 'strict': False} @classmethod def get(cls, id): coll_name = cls._meta['collection'] key = OBJ_KEY.format(coll_name=coll_name, id=id) rs = cache.get(key) if rs: return cls.from_json( rs) # Converts json data to an unsaved document instance rs = cls.objects.get(id=id) cache.set(key, rs.to_json()) # Convert this document to JSON. return rs @classmethod def get_multi(cls, ids): return [cls.get(i) for i in ids if i] @classmethod def get_or_create(cls, **kwargs): try: return cls.objects.get(id=kwargs['id']) except DoesNotExist: kwargs.update({'update_at': datetime.now()}) model = cls(**kwargs) model.save() return model @classmethod def get_sample_ids(cls, size): samples = list(cls.objects.aggregate({'$sample': {'size': size}})) return [s['_id'] for s in samples]
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 OfferStats(db.EmbeddedDocument): store_count = db.IntField(default=0) views = db.IntField(default=0) add_to_cart = db.IntField(default=0) orders = db.IntField(default=0) popularity = db.FloatField(default=0)
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 CartOffer(db.EmbeddedDocument): offer = db.ReferenceField('Offer') quantity = db.IntField(default=1) variant = db.StringField(default=None)
class OfferSpecial(db.Document): is_active = db.BooleanField(default=False) type = db.StringField(choices=(('real', u'Понизить цену'), ('fake', u'Повысить цену')), default='real', verbose_name=u'Тип акции') price_type = db.StringField(choices=(('percent', u'На проценты'), ('new', u'На сумму')), default='percent') price_value = db.IntField(required=True) timer_type = db.StringField(choices=(('date', u'До даты'), ('time', u'По времени')), default='date') timer_settings = db.DictField(required=True) created_at = db.DateTimeField() prices = db.EmbeddedDocumentField('OfferPrices') def __unicode__(self): return str(self.id) @classmethod def create_or_update(cls, offer, form): special = offer.get_special or cls() form.populate_obj(special) special.populate_price(offer) special.set_created_at() special.save() offer.update(set__special=special) return special def create_from_self(self): offer = Offer.objects(special=self).first() if offer: data = deepcopy(self._data) data.pop('id') data.pop('prices') new = self.__class__(**data) return new def set_created_at(self): self.is_active = True self.created_at = datetime.now() def populate_price(self, offer): if self.type == 'real': if not self.prices: self.prices = offer.price if self.price_type == 'percent': new_price = self.prices.ru * (1 - float(self.price_value) / 100) elif self.price_type == 'new': new_price = self.prices.ru - self.price_value offer.update(set__price__ru=new_price) offer.reload() else: if not self.prices: self.prices = offer.price if self.price_type == 'percent': new_price = self.prices.ru * (1 + float(self.price_value) / 100) elif self.price_type == 'new': new_price = self.prices.ru + self.price_value self.prices.ru = new_price def get_timer(self): if self.timer_type == 'date': date = datetime.strptime(self.timer_settings.get('timer_date'), "%d/%m/%Y").date() return date + timedelta(days=1) elif self.timer_type == 'time': now = datetime.now() delta = now - self.created_at days_step = int(self.timer_settings.get('timer_days', 1)) if self.timer_settings.get('timer_repeat') == 'on' and delta.days > days_step: full = delta.days - (delta.days % days_step) + days_step new_delta = timedelta(days=full) else: new_delta = timedelta(days=days_step) return (self.created_at + new_delta + timedelta(days=1)).date() @property def timer(self): return self.get_timer().strftime('%Y/%m/%d') @cached_property def is_over(self): now = datetime.now() is_over = now.date() >= self.get_timer() if is_over: offer = Offer.objects(special=self).first() if offer: self.remove(offer) else: self.update(set__is_active=False) return is_over def remove(self, offer): atomic = dict(set__special=None) if self.type == 'real': atomic['set__price'] = self.prices offer.update(**atomic) self.delete()
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 CartTotal(db.EmbeddedDocument): cost = db.FloatField(default=0) count = db.IntField(default=0)
class OfferVariant(db.EmbeddedDocument): store_count = db.IntField() aid = db.IntField() name = db.StringField()
class RegionDelivery(db.EmbeddedDocument): method = db.StringField(max_length=400) id = db.IntField() price = db.FloatField()
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 Region(db.Document): id = db.IntField(primary_key=True) name = db.StringField(max_length=400) popularity = db.IntField(default=0) deliveries = db.ListField(db.EmbeddedDocumentField('RegionDelivery'), default=[]) updated_at = db.DateTimeField() meta = { 'ordering': ['-popularity', '+id'], 'indexes': ['name'] } def __unicode__(self): return self.name def __str__(self): return u'%s(%s)' % (self.__class__.__name__, self.id) def int_popularity(self): popularity = (self.popularity or 0) + 1 try: self.update(set__popularity=popularity) except db.OperationError: self.popularity = popularity self.save() def update_deliveries(self, methods): now = datetime.now() try: self.update(set__deliveries=methods, set__updated_at=now) except db.OperationError: self.deliveries = methods self.updated_at = now self.save() def get_delivery_prices(self, result, aid=None): d_methods = {str(d.id): {'id': d.id, 'method': d.name} for d in DeliveryMethod.objects()} if not aid: aids = Offer.objects(available=True).distinct('aid') aid = random.choice(aids) methods = [] if len(result): deliveries = result[0].deliveries for delivery in deliveries: d = d_methods.get(delivery.id, None) if d: prices = [] for payment in delivery.payments: price = payment.sum if price is not None and price > 0 and price not in prices: prices.append(price) if len(prices): price = min(prices) d['price'] = price methods.append(RegionDelivery(**d)) return methods
class CategoryStats(db.EmbeddedDocument): views = db.IntField(default=0) items = db.IntField(default=0)