class Dispatcher(db.Document): path = db.StringField(primary_key=True) key = db.StringField(max_length=255) args = db.DictField() # dispatcher_map = DISPATCHER_KEYS meta = {'indexes': ['path']} @classmethod def create_or_change(cls, obj): # print getattr(obj, 'old_path'), getattr(obj, 'path') if hasattr(obj, 'path'): old_path = getattr(obj, 'old_path', None) dispatcher_key = obj.dispatcher_key path = obj.path if old_path and old_path != path: # Если пути не совпадают, взять объект по старому пути, # обновить путь. Старый путь сохранить в новый объект # для редиректа dispatcher = cls.objects(path=old_path).first() if dispatcher: dispatcher.delete() dispatcher = cls(path=path, key=dispatcher_key) dispatcher.save() @classmethod def get_by_path(cls, path): dispatcher = cls.objects.get_or_404(path=path) return dispatcher.key
class User(BaseModel): name = db.StringField() picture = db.StringField() @property def url(self): return USER_URL.format(self.id)
class Order(db.Document): apishop_id = db.StringField(required=True) offers = db.ListField(db.EmbeddedDocumentField('OrderOffer'), default=[]) ordered_at = db.DateTimeField() userinfo = db.DictField(default={}) delivery_info = db.DictField(default={}) comment = db.StringField() @property def get_delivery(self): return dict(price=self.delivery_info.get('delivery_price'), total=self.delivery_info.get('order_sum')) \ if self.delivery_info.get('delivery_price', None) is not None else None @property def get_total(self): return sum([offer.price * offer.quantity for offer in self.offers]) def get_relevant_offers(self, max_items=4): offer_ids_not_to = [item.offer.id for item in self.offers] offer_ids = Offer.objects(available=True, id__nin=offer_ids_not_to).distinct('id') length = len(offer_ids) if length: try: ids = random.sample(offer_ids, max_items) except ValueError: ids = random.sample(offer_ids, length) return Offer.objects(id__in=ids) def get_timer(self): timer = self.ordered_at + timedelta(minutes=5) - timedelta(seconds=20) if timer < datetime.now(): return False return timer.strftime('%Y/%m/%d %H:%M:%S') def add_offer(self, offer, quantity=1, variant=None): o = OrderOffer(offer=offer, price=offer.get_price(), oldprice=offer.get_oldprice or 0.0, quantity=quantity, variant=variant) self.update(push__offers=o) self.reload() def populate_offers(self, sended_offers): if not isinstance(sended_offers, (list, tuple)): sended_offers = [sended_offers] for sended_offer in sended_offers: offer = Offer.objects(articul=sended_offer.get('articul')).first() if offer: self.offers.append(OrderOffer(offer=offer, price=offer.get_price(), oldprice=offer.get_oldprice or 0.0, quantity=sended_offer.get('quantity', 1), variant=sended_offer.get('variant', None)))
class Movie(BaseModel): name = db.StringField() mark = db.StringField() picture = db.StringField() meta = {'indexes': ['name']} @property def url(self): return MOVIE_URL.format(self.id)
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 SmallBanner(Banner): bg_image = db.StringField(verbose_name=u'Фоновое изображение') link_to = db.StringField(verbose_name=u'Ссылка') header = db.StringField(max_length=100, verbose_name=u'Заголовок') bottom = db.StringField(max_length=200, verbose_name=u'Нижний текст') bottom_size = db.StringField(choices=(('sm', u'Маленький'), ('bg', u'Большой')), verbose_name=u'Размер нижнего текста') image_size = (400, 320) def generate_image_name(self): name = slugify(self.header) if self.bottom: name = slugify('-'.join([name, slugify(self.bottom)])) return name
class PathMixin(SlugMixin): path = db.StringField(unique=True) def _check_path_exists(self): filters = dict(path=self.path) if self.id: filters['id__ne'] = self.id exist = self.__class__.objects(**filters).count() return exist def validate_path(self): self.old_path = self.path or None self.validate_slug() suffix = 1 if hasattr(self, 'parent') and self.parent and self.parent != self: self.path = '/'.join([self.parent.path, self.slug]) else: if hasattr(self, 'path_prefix'): self.path = '/'.join([getattr(self, 'path_prefix'), self.slug]) else: self.path = self.slug while self._check_path_exists(): self.path = '-'.join([self.path, str(suffix)]) suffix += 1 def save(self, *args, **kwargs): self.validate_path() super(PathMixin, 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 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 SlugMixin(object): slug = db.StringField(max_length=255, verbose_name=u'Ссылка') def validate_slug(self, name=None): if self.slug: self.slug = slugify(self.slug) else: self.slug = slugify(name if name else self.name) self.slug = slugify(self.slug)
class Subscriber(IntIDMixin, db.Document): name = db.StringField(max_length=200, required=True) email = db.StringField(max_length=200, required=True) subscribed_at = db.DateTimeField(default=datetime.now()) is_active = db.BooleanField(default=True) def __unicode__(self): return '<Subscriber {}: {}>'.format(self.id, self.email) def mark_subscribed(self): userinfo = session.get('userinfo', None) if not userinfo: session['userinfo'] = dict(fullname=self.name, email=self.email) else: if 'fullname' not in userinfo: userinfo['fullname'] = self.name if 'email' not in userinfo: userinfo['email'] = self.email session['is_subscribed'] = True
class Vendor(DispatcherMixin, IntIDMixin, PathMixin, db.Document): name = db.StringField(max_length=255, verbose_name=u'Название') path_prefix = 'vendor' @classmethod def get_or_create_by_name(cls, name): vendor = cls.objects(name=name.strip()).first() if not vendor: vendor = cls(name=name.strip()) vendor.save() return vendor
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 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 WideBanner(Banner): bg_image = db.StringField(verbose_name=u'Фоновое изображение') link_to = db.StringField(verbose_name=u'Ссылка') left_top = db.StringField(max_length=100, verbose_name=u'Слева сверху') left_bot = db.StringField(max_length=100, verbose_name=u'Слева снизу') right_top = db.StringField(max_length=100, verbose_name=u'Справа сверху') right_bot = db.StringField(max_length=100, verbose_name=u'Справа снизу') image_size = (320, 320) def generate_image_name(self): return slugify('-'.join([self.left_bot, self.right_bot]))
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 Feature(db.Document): """A Point Feature valid Feature: - geometry: type: "Point", coordinates: <two-dim Array> - type: "Feature" - properties: a dict """ type = db.StringField(default='Feature', choices=['Feature']) geometry = db.PointField(null=False) properties = db.DictField() meta = { 'allow_inheritance': True, 'abstract': True, 'queryset_class': FeatureQuerySet } def to_feature(self): data = { 'id': str(self.id), 'type': self.type, 'geometry': self.geometry, 'properties': self.properties, 'timestamp': str(self.id.generation_time), 'uri': url_for('api.get_item', collection=self.slug, item_id=self.id, _external=True) } return data def from_dict(self, data): self.geometry = data.get('geometry') self.properties = data.get('properties')
class Admin(db.Document): _pwd = db.StringField(db_field='pwd') @property def pwd(self): return self._pwd @pwd.setter def pwd(self, value): try: self._pwd = bcrypt.generate_password_hash(value.encode('utf-8')) except (ValueError, AttributeError): raise ValueError('Cant set password. Probably empty value') def auth(self, plaintext): try: return bcrypt.check_password_hash(self._pwd.encode('utf-8'), plaintext.encode('utf-8')) except (ValueError, AttributeError, KeyError): return False
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 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 BaseModel(db.Document): id = db.StringField(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) rs = cls.objects.get(id=id) cache.set(key, rs.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 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 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 RegionDelivery(db.EmbeddedDocument): method = db.StringField(max_length=400) id = db.IntField() price = db.FloatField()
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 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 Role(db.Document, RoleMixin): name = db.StringField(max_length=255, unique=True) description = db.StringField(max_length=255) def __unicode__(self): return self.name
class Banner(db.Document): banner_type = db.StringField(choices=type_choices, default=u'small', max_length=100, verbose_name=u'Тип баннера') bg_color = db.StringField(default='#333333', max_length=7, verbose_name=u'Цвет фона') is_enabled = db.BooleanField(default=True) meta = { 'allow_inheritance': True } @classmethod def get_class_by_group(cls, group): return eval('{}Banner'.format(group.capitalize())) @classmethod @cache.memoize(60) def get_banners_grouped(cls, only_enabled=True): filters = {} if only_enabled: filters['is_enabled'] = True banners = cls.objects(**filters) grouped_banners = {} for banner in banners: if banner.banner_type in grouped_banners: grouped_banners[banner.banner_type].append(banner) else: grouped_banners[banner.banner_type] = [banner] return grouped_banners @classmethod def get_banners(cls, group=None, only_enabled=True): banners = cls.get_banners_grouped(only_enabled) grouped_banners = {} for g, bs in banners.items(): path = request.path new_bs = [] for b in bs: if path != b.link_to: new_bs.append(b) max = max_banners_items.get('MAX_{}_BANNERS'.format(g.upper())) random.shuffle(new_bs) if len(new_bs) > max: new_bs = new_bs[:max] grouped_banners[g] = new_bs if group: return grouped_banners.get(group, []) return grouped_banners @property def get_link_to(self): if not self.link_to: return url_for('site.index') return '/{}/'.format(self.link_to.strip('/')) def generate_image_name(self): return str(self.id) def upload_image(self, file): size = self.image_size or (300, 300) original = upload_file(file, self.generate_image_name(), 'banners') image = create_offer_image(original, format='png', width=size[0], height=size[1], suffix='b', fill=0, quality=100) if os.path.exists(os.path.join(current_app.config.get('MEDIA_DIR'), original)): os.remove(os.path.join(current_app.config.get('MEDIA_DIR'), original)) return image def save(self, *args, **kwargs): cache.delete_memoized(Banner.get_banners_grouped) super(Banner, self).save(*args, **kwargs)