class Role(db.Model): uuid = db.Attribute(indexed=False) name = db.Attribute(required=True) description = db.Attribute(indexed=False) created_at = db.DateTimeField() updated_at = db.DateTimeField() permission = db.IntegerField(required=True) def __repr__(self): return "<{}-{:04X}>".format(self.name, self.permission) def __str__(self): return self.__repr__() def __unicode__(self): return self.__repr__() def to_json(self, user): rst = self.attributes_dict rst["created_at"] = format_datetime(self.created_at) rst["updated_at"] = format_datetime(self.updated_at) rst["created_by"] = user.id rst["updated_by"] = user.id rst["id"] = self.id return rst
class User(db.Model): uuid = db.Attribute(indexed=False) name = db.Attribute(required=True) slug = db.Attribute(indexed=False) email = db.Attribute(required=True) avatar = db.Attribute(required=True, indexed=False, default=DEFAULT_AVATAR_URL) password = db.Attribute(required=True, indexed=False) image = db.Attribute(indexed=False) cover = db.Attribute(indexed=False) bio = db.Attribute(indexed=False) website = db.Attribute(indexed=False) facebook = db.Attribute(indexed=False) twitter = db.Attribute(indexed=False) accessibility = db.BooleanField(indexed=False) status = db.Attribute() language = db.Attribute(indexed=False) visibility = db.BooleanField() meta_title = db.Attribute(indexed=False) meta_description = db.Attribute(indexed=False) tour = db.Attribute(indexed=False) last_login = db.DateTimeField() created_at = db.DateTimeField() updated_at = db.DateTimeField() role = db.ListField(Role, required=True) def to_json(self): rst = self.attributes_dict del rst['password'] del rst['role'] rst['id'] = self.id rst['last_login'] = format_datetime(self.last_login) rst['created_at'] = format_datetime(self.created_at) rst['updated_at'] = format_datetime(self.updated_at) rst['roles'] = [role.to_json(self) for role in self.role] return rst def is_authenticated(self): return True def is_active(self): return True def is_anonymous(self): return False def get_id(self): return str(self.id)
class Link(r_models.Model): """Model that represents a shortened URL.""" short_url = r_models.Attribute(unique=True) url = r_models.Attribute(required=True) date_submitted = r_models.DateTimeField(auto_now_add=True) usage_count = r_models.IntegerField(default=0, indexed=True) is_custom = r_models.BooleanField() def __unicode__(self): return '%s : %s' % (self.url, self.short_url) def save(self, *args, **kwargs): if hasattr(self, 'id'): super(self.__class__, self).save(*args, **kwargs) return if self.is_custom: pass else: self.short_url = utils.get_short_url() while True: existing_link = self.__class__.objects.filter( short_url=self.short_url) if existing_link: if self.is_custom: raise ValueError('Custom url already exists') else: self.short_url = utils.get_short else: break super(self.__class__, self).save(*args, **kwargs)
class Upload(db.Model): post = db.IntegerField(required=True) filename = db.Attribute(required=True) local_path = db.Attribute(required=True) cloud = db.IntegerField(required=True) url = db.Attribute(required=True) done_at = db.DateTimeField(auto_now_add=True)
class CommentModel(models.Model): author = models.Attribute() message = models.Attribute() contentId=models.Attribute() commentId=models.Attribute() commentTo=models.Attribute() gmtCreate = models.DateTimeField(auto_now_add=True)
class Trade(models.Model): exec_id = models.Attribute(required=True) order = models.ReferenceField('Order') trade_time = models.DateTimeField(required=True) price = models.FloatField(required=True, indexed=False) volume = models.FloatField(required=True, indexed=False) closed_volume = models.FloatField(required=True, default=0.0, indexed=False) commission = models.FloatField(required=True, default=0.0, indexed=False) profit = models.FloatField(required=True, default=0.0, indexed=False) @property def opened_volume(self): return self.volume - self.closed_volume @property def amount(self): return self.order.instrument.amount(self.price, self.volume) @property def opened_amount(self): return self.order.instrument.amount(self.price, self.opened_volume) def on_trade(self, price, volume, trade_time, exec_id, is_open): self.price = float(price) self.volume = float(volume) self.trade_time = trade_time self.exec_id = exec_id self.commission = self.order.instrument.calc_commission( price, volume, is_open) assert self.is_valid(), self.errors self.save() def on_close(self): for orig_trade in self.order.orig_order.trades: if abs(self.closed_volume) >= abs(self.volume): break if orig_trade.opened_volume == 0.0: continue if abs(orig_trade.opened_volume) < abs(self.opened_volume): vol = orig_trade.opened_volume else: vol = -self.opened_volume logger.debug('Trade {0} against {1} close volume={2}'.format( self.exec_id, orig_trade.exec_id, vol)) self.closed_volume -= vol orig_trade.closed_volume += vol if self.order.instrument.indirect_quotation: self.profit += self.order.instrument.amount( orig_trade.price, vol) - self.order.instrument.amount( self.price, vol) else: self.profit += self.order.instrument.amount( self.price - orig_trade.price, vol) assert orig_trade.is_valid(), orig_trade.errors orig_trade.save() assert self.is_valid(), self.errors self.save()
class GameModel(models.Model): desk = models.Attribute() game_time = models.DateTimeField(auto_now_add=True) all_bet = models.IntegerField(default=0) all_paid = models.IntegerField(default=0) game_result = models.ListField(int) bank_cards = models.ListField(int) player_cards = models.ListField(int)
class ContentModel(models.Model): contentId=models.Attribute() gmtCreate = models.DateTimeField(auto_now_add=True) author = UserModel section=models.Attribute() content = models.Attribute() title=models.Attribute() imgList=models.ListField(str)
class LogEntry(rmodels.Model): num = rmodels.IntegerField() time = rmodels.FloatField() date = rmodels.DateTimeField(auto_now_add=True) class Meta: verbose_name = 'Search index update statistic' verbose_name_plural = 'Search index update statistic' db = default_connection
class DomainInfo(re_models.Model): name = re_models.Attribute(required=True) created_at = re_models.DateTimeField(auto_now_add=True) res_code = re_models.IntegerField(default=0) alert = re_models.BooleanField(default=False) new_msg = re_models.BooleanField(default=True) address = re_models.ListField(str) no_ip = re_models.ListField(str) info = re_models.Attribute()
class PlayerModel(models.Model): playerId = models.Attribute() player_phone = models.Attribute(required=True) player_name = models.Attribute(required=True) player_password = models.Attribute() last_login = models.DateTimeField() player_points = models.Counter() player_vip = models.IntegerField(default=0) player_desk = models.Attribute()
class DeviceInfoInterface(models.Model): '''''' timestamp = models.DateTimeField(required=True) cpu_usage = models.Attribute() mem_usage = models.Attribute() net_usage = models.Attribute() net_flow = models.ListField(dict) proc_info = models.ListField(dict)
class UserInterface(models.Model): '''''' id = models.IntegerField(required=True, unique=True) user = models.Attribute(required=True, unique=True) pwd = models.Attribute() description = models.Attribute() email = models.Attribute() avatar = models.Attribute() last_login = models.DateTimeField(auto_now=True)
class Stock(models.Model): sc_code = models.IntegerField(required=True) sc_name = models.Attribute(required=True) created_at = models.DateTimeField(auto_now_add=True) sc_open = models.FloatField() sc_high = models.FloatField() sc_low = models.FloatField() sc_close = models.FloatField() sc_prevclose = models.FloatField() sc_volume = models.IntegerField()
class Tag(db.Model): uuid = db.Attribute(indexed=False) name = db.Attribute(indexed=False) slug = db.Attribute(indexed=False) hidden = db.BooleanField() parent = db.Attribute(indexed=False) image = db.Attribute(indexed=False) meta_title = db.Attribute(indexed=False) meta_description = db.Attribute(indexed=False) created_at = db.DateTimeField() updated_at = db.DateTimeField() def to_json(self): rst = self.attributes_dict rst["id"] = self.id rst["created_at"] = format_now_datetime(self.created_at) rst["updated_at"] = format_now_datetime(self.updated_at) rst["created_by"] = None rst["updated_by"] = None return rst
class AccessToken(models.Model): """ Model to store, in redis (via redisco) all tokens and their status """ uid = models.Attribute(required=True, indexed=True, unique=True) login = models.Attribute(required=True, indexed=True) token = models.Attribute(required=True, indexed=True) backend = models.Attribute(required=True, indexed=True) status = models.IntegerField(required=True, indexed=True, default=200) last_use = models.DateTimeField(auto_now_add=True, auto_now=True) last_message = models.Attribute() def __unicode__(self): return str(self) def __str__(self): return self.uid def save(self): is_new = self.is_new() result = super(AccessToken, self).save() if result == True and is_new: self.release() return result def is_valid(self): """ Overrive the default method to save the uid, which is unique (by concatenating backend and token) """ self.uid = '%s:%s:%s' % (self.backend, self.login, self.token) return super(AccessToken, self).is_valid() def lock(self): """ Set the token as currently used """ return connection.srem(AVAILABLE_LIST_KEY, self.uid) def release(self): """ Set the token as not currently used """ return connection.sadd(AVAILABLE_LIST_KEY, self.uid) def set_status(self, code, message): """ Set a new status and message for this token """ self.status = code self.last_message = message self.save()
class LinkAccess(r_models.Model): """Model that represents access of a shortened url.""" link = r_models.ReferenceField(Link) country = r_models.Attribute() region = r_models.Attribute() referrer = r_models.Attribute() lat = r_models.Attribute() lng = r_models.Attribute() ip = r_models.Attribute() atime = r_models.DateTimeField(auto_now_add=True) def save(self, *args, **kwargs): super(LinkAccess, self).save(*args, **kwargs) thread.start_new_thread(geotag_link_access, (self, True))
class Comment(db.Model): # 评论者的用户名 author_name = db.Attribute() # 评论者的邮箱,如果没有设置头像会根据这个信息从gravatar获取头像 author_email = db.Attribute() # 评论者的URL,评论者头像或者名字会跳转到改URL author_url = db.Attribute() # 评论者的IP ip = db.Attribute() # 评论者User Agent信息,通常包括浏览器版本、引擎、设备等信息 agent = db.Attribute() # 这条评论被【赞】的次数,该属性导入意义不大,会在被喜欢之后重新统计 likes = db.IntegerField() # 对这条评论点了【举报】的次数 reports = db.IntegerField() # 评论发表时间。 created_at = db.DateTimeField() # 评论状态。 status = db.IntegerField()
class DurationEvent(models.Model): name = models.CharField() started = models.DateTimeField() duration = models.TimeDeltaField(default=timedelta(seconds=20))
class Url(models.Model): long_url = models.CharField(max_length=1000, required=True) short_url = models.CharField(max_length=5, required=True) created_at = models.DateTimeField(auto_now_add=True)
class RoomModel(models.Model): roomId=models.Attribute() gmtCreate = models.DateTimeField(auto_now_add=True) users = models.ListField(str)
class Post(models.Model): title = models.CharField() date_posted = models.DateTimeField() created_at = models.DateTimeField(auto_now_add=True)
class Audit(object): created_at = models.DateTimeField(auto_now_add=True) last_modified = models.DateTimeField(auto_now=True)
class Order(models.Model): OS_NONE, OS_NEW, OS_CANCELED, OS_FILLED, OS_CLOSING, OS_CLOSED, OS_REJECTED = range( 7) account = models.ReferenceField('Account') local_id = models.Attribute() sys_id = models.Attribute(default='') strategy_code = models.Attribute(default='') instrument = models.ReferenceField(Instrument) is_long = models.BooleanField(indexed=False) is_open = models.BooleanField(indexed=True) order_time = models.DateTimeField() price = models.FloatField(indexed=False) volume = models.FloatField(indexed=False) status = models.IntegerField(default=OS_NONE) orig_order = models.ReferenceField('Order', related_name='close_orders') stop_profit_offset = models.FloatField(indexed=False, default=0.0) # 止赢偏离值 stoploss = models.FloatField(indexed=False, default=0.0) # 止损价 stopprofit = models.FloatField(indexed=False, default=0.0) # 止赢价 def __repr__(self): return u'<Order: {0.id}({0.instrument}:{0.opened_volume})>'.format( self) def is_closed(self): return self.status == Order.OS_CLOSED @property def currency(self): return self.instrument.quoted_currency @property def can_close(self): return self.status == Order.OS_FILLED @property def can_cancel(self): if self.status in (Order.OS_NONE, Order.OS_NEW): return True elif self.status == Order.OS_FILLED: if abs(self.filled_volume) < abs(self.volume): return True return False @property def trades(self): return Trade.objects.filter(order_id=self.id).order('trade_time') @property def filled_volume(self): return sum([trade.volume for trade in self.trades]) @property def closed_volume(self): return sum([trade.closed_volume for trade in self.trades]) @property def opened_volume(self): """ 剩余开仓量 """ return sum([trade.opened_volume for trade in self.trades]) @property def opened_amount(self): return sum([trade.opened_amount for trade in self.trades]) @property def commission(self): return sum([trade.commission for trade in self.trades]) @property def real_profit(self): return sum([trade.profit for trade in self.trades]) @property def trade_amt(self): return sum([trade.amount for trade in self.trades]) @property def avg_fill_price(self): if self.filled_volume: if self.instrument.indirect_quotation: return self.filled_volume * self.instrument.multiplier / self.trade_amt else: return self.trade_amt / (self.filled_volume * self.instrument.multiplier) return None @property def cur_price(self): return current_price(self.instrument.secid, self.opened_volume > 0) @property def strategy(self): return STRATEGIES.get(self.strategy_code) def delete(self, *args, **kwargs): for t in self.trades: t.delete() super(Order, self).delete(*args, **kwargs) def update_index_value(self, att, value): assert att in ('status', 'is_open', 'local_id', 'sys_id') pipeline = self.db.pipeline() # remove from old index indkey = self._index_key_for_attr_val(att, getattr(self, att)) pipeline.srem(indkey, self.id) pipeline.srem(self.key()['_indices'], indkey) # add to new index # in version 0.1.4 there is a bug in self._add_to_index(att, value, pipeline): # the val paramter doesnot work, it's ignored. # so i have to hardcode it as following t, index = self._index_key_for(att, value) if t == 'attribute': pipeline.sadd(index, self.id) pipeline.sadd(self.key()['_indices'], index) elif t == 'list': for i in index: pipeline.sadd(i, self.id) pipeline.sadd(self.key()['_indices'], i) elif t == 'sortedset': zindex, index = index pipeline.sadd(index, self.id) pipeline.sadd(self.key()['_indices'], index) descriptor = self.attributes[att] score = descriptor.typecast_for_storage(value) pipeline.zadd(zindex, self.id, score) pipeline.sadd(self.key()['_zindices'], zindex) # set db value pipeline.hset(self.key(), att, value) pipeline.execute() # set instance value setattr(self, att, value) def update_status(self, value): value = int(value) assert 0 <= value < 7 logger.debug('update order {2} status from {0} to {1}'.format( getattr(self, 'status'), value, self.sys_id)) self.update_index_value('status', value) def change_to_open_order(self): self.update_index_value('is_open', 1) def update_local_id(self, value): value = str(value) self.update_index_value('local_id', value) def update_sys_id(self, value): value = str(value) self.update_index_value('sys_id', value) def update_float_value(self, att, value): assert att in ('stoploss', 'stopprofit', 'stop_profit_offset', 'volume') value = float(value) self.db.hset(self.key(), att, value) setattr(self, att, value) def update_stopprice(self, stoploss=None, stopprofit=None): if stoploss is not None: self.update_float_value('stoploss', stoploss) if stopprofit is not None: self.update_float_value('stopprofit', stopprofit) def update_stop_profit_offset(self, value): self.update_float_value('stop_profit_offset', value) def margin(self, cur_price=None): cur_price = cur_price or self.cur_price return self.instrument.calc_margin(cur_price, self.opened_volume) def float_profit(self, cur_price=None): cur_price = cur_price or self.cur_price profit = self.instrument.amount( cur_price, self.opened_volume) - self.opened_amount if self.instrument.indirect_quotation: profit *= -1 return profit def on_new(self, orderid, instid, direction, price, volume, exectime): instrument = Instrument.objects.filter(secid=instid).first() #assert self.is_open is not None self.sys_id = orderid self.instrument = instrument self.is_long = direction self.price = float(price) self.volume = float(volume) self.order_time = exectime self.status = Order.OS_NEW assert self.is_valid(), self.errors self.save() def on_trade(self, price, volume, tradetime, execid): assert self.is_open is not None # check duplicate trade if Trade.objects.filter(exec_id=execid): logger.debug(u'EXECID {0} 已经存在!'.format(execid)) return False if not self.is_long: volume = -volume t = Trade(order=self) t.on_trade(price, volume, tradetime, execid, self.is_open) self.update_status(Order.OS_FILLED) logger.info(u'<策略{0}>成交回报: {1}{2}仓 合约={3} 价格={4} 数量={5}'.format( self.strategy_code, u'开' if self.is_open else u'平', u'多' if self.is_long == self.is_open else u'空', self.instrument.name, price, volume, )) return t def set_stopprice(self, price, offset_loss=0.0, offset_profit=0.0): # 静态止赢价 if offset_profit and not self.stopprofit: if self.is_long: stopprofit = price + offset_profit else: stopprofit = price - offset_profit self.update_stopprice(stopprofit=stopprofit) logger.debug('Order {0} set stop profit price to {1}'.format( self.sys_id, self.stopprofit)) # 浮动止损价 if offset_loss: if self.is_long: stoploss = price - offset_loss if self.stoploss and stoploss <= self.stoploss: return else: stoploss = price + offset_loss if self.stoploss and stoploss >= self.stoploss: return self.update_stopprice(stoploss) logger.debug('Order {0} set stop loss price to {1}'.format( self.sys_id, self.stoploss)) def on_close(self, trade): trade.on_close() if abs(self.orig_order.closed_volume) >= abs( self.orig_order.filled_volume): self.orig_order.update_status(Order.OS_CLOSED) logger.debug(u'订单{0}已全部平仓'.format(self.orig_order.sys_id)) if (abs(self.closed_volume) >= abs(self.volume)) or (abs( self.closed_volume) >= abs(self.orig_order.filled_volume)): self.update_status(Order.OS_CLOSED) logger.debug(u'订单{0}已全部平仓'.format(self.sys_id))
class Post(models.Model): name = models.CharField() date = models.DateTimeField()
class Account(models.Model): code = models.Attribute(required=True) default_currency = models.Attribute(required=True, indexed=False, default='USD') last_trade_time = models.DateTimeField(indexed=False) balances = models.ListField(Balance, indexed=False) def __init__(self, *args, **kwargs): super(Account, self).__init__(*args, **kwargs) self.real_profits = 0.0 @property def orders(self): return Order.objects.filter(account_id=self.id) def opened_orders(self, instrument=None, strategy_code=''): key = 'opened_orders:{0}:{1}:{2}'.format(self.id, instrument, strategy_code) cached = self.db.get(key) if cached: return [Order.objects.get_by_id(oid) for oid in json.loads(cached)] else: queryset = self.orders if instrument: queryset = queryset.filter(instrument_id=instrument.id) if strategy_code: queryset = queryset.filter(strategy_code=strategy_code) orders = list(queryset.filter(status=Order.OS_FILLED)) orders.extend(list(queryset.filter(status=Order.OS_CLOSING))) cached = json.dumps([o.id for o in orders]) self.db.setex(key, cached, 1) return orders def untraded_orders(self, instrument=None, strategy_code=''): queryset = self.orders if instrument: queryset = queryset.filter(instrument_id=instrument.id) if strategy_code: queryset = queryset.filter(strategy_code=strategy_code) orders = list(queryset.filter(status=Order.OS_NEW)) queryset = queryset.filter(status=Order.OS_FILLED) orders.extend( [o for o in queryset if abs(o.filled_volume) < abs(o.volume)]) return orders def combined_positions(self): qs = sorted(self.opened_orders(), key=attrgetter('instrument_id')) groups = groupby(qs, attrgetter('instrument')) for inst, orders in groups: yield inst, sum([o.opened_volume for o in orders]) @property def balance(self): return sum( [b.convert_to(self.default_currency) for b in self.balances]) @property def available(self): if getattr(self, '_available', None) is None: return self.balance - self.margins + self.float_profits return self._available @property def margins(self): return sum([ convert_currency(o.margin(), o.currency, self.default_currency) for o in self.opened_orders() ]) @property def float_profits(self): return sum([ convert_currency(o.float_profit(), o.currency, self.default_currency) for o in self.opened_orders() ]) def open_orders(self, strategy_code=''): queryset = self.orders.filter(is_open=True) if strategy_code: queryset = queryset.filter(strategy_code=strategy_code) return queryset def balance_in(self, ccy): return self.get_balance_object(ccy).value def get_balance_object(self, currency): try: balance = [b for b in self.balances if b.currency == currency][0] except IndexError: balance = Balance(currency=currency, value=0.0) balance.save() self.balances.append(balance) self.save() return balance def book(self, change, currency, memo): balance = self.get_balance_object(currency) balance.value += float(change) assert balance.is_valid(), balance.errors balance.save() msg = u'{3}:{0}{1}, 余额{2}{1}'.format(change, currency, balance.value, memo) if u'利润' in msg: logger.info(msg) else: logger.debug(msg) def deposit(self, quantity, currency=''): currency = currency or self.default_currency self.book(quantity, currency, u'转入资金') def set_balance(self, quantity, currency=''): currency = currency or self.default_currency balance = self.get_balance_object(currency) balance.value = float(quantity) assert balance.is_valid(), balance.errors balance.save() logger.debug(u'设置资金余额:{0}{1}'.format(quantity, currency)) def set_available(self, available): self._available = available def create_order(self, local_order_id, inst=None, price=None, volume=None, is_open=None, strategy_code='', orig_order=None): assert is_open is None or is_open == (orig_order is None), (is_open, orig_order) neworder = Order.objects.filter(local_id=local_order_id).first() if not neworder: neworder = Order(local_id=local_order_id, instrument=inst) try: neworder.price = float(price) except: pass try: neworder.volume = float(volume) except: pass logger.debug('NEWORDER local_id={0}'.format(neworder.local_id)) neworder.update_attributes(account=self, is_open=is_open, strategy_code=strategy_code) if orig_order: neworder.orig_order = orig_order if not neworder.strategy_code: neworder.strategy_code = orig_order.strategy_code assert neworder.is_valid(), neworder.errors neworder.save() return neworder def on_trade(self, order, execid, price, volume, tradetime): trade = order.on_trade(price, volume, tradetime, execid) if not trade: return self.book(-trade.commission, order.currency, u'<策略{0}>收取手续费'.format(order.strategy_code)) if not order.is_open: # 平仓 order.on_close(trade) self.book(trade.profit, order.currency, u'<策略{0}>获取利润'.format(order.strategy_code)) self.real_profits += trade.profit if not self.last_trade_time or self.last_trade_time < tradetime: self.last_trade_time = tradetime self.save()
class HandSelModel(models.Model): sender = models.Attribute() receiver = models.Attribute() points = models.IntegerField() date = models.DateTimeField(auto_now_add=True) success = models.BooleanField(default=False)
class BaseModel(models.Model): created = models.DateTimeField(auto_now_add=True)
class AccessToken(models.Model): """ Model to store, in redis (via redisco) all tokens and their status """ uid = models.Attribute(required=True, indexed=True, unique=True) login = models.Attribute(required=True, indexed=True) token = models.Attribute(required=True, indexed=True) backend = models.Attribute(required=True, indexed=True) status = models.IntegerField(required=True, indexed=True, default=200) last_use = models.DateTimeField() last_message = models.Attribute() suspended_until = models.DateTimeField() def __unicode__(self): return str(self) def __str__(self): return self.uid def save(self): is_new = self.is_new() self.last_use = datetime.utcnow() result = super(AccessToken, self).save() if result is True and is_new: self.release() return result def is_valid(self): """ Override the default method to save the uid, which is unique (by concatenating backend and token) """ self.uid = '%s:%s:%s' % (self.backend, self.login, self.token) return super(AccessToken, self).is_valid() def lock(self): """ Set the token as currently used """ return connection.srem(AVAILABLE_LIST_KEY, self.uid) def release(self): """ Set the token as not currently used """ return connection.sadd(AVAILABLE_LIST_KEY, self.uid) def set_status(self, code, message): """ Set a new status and message for this token """ self.status = code self.last_message = message self.save() def suspend(self, suspended_until=None, message=None): """ Suspend the token until the given utc timestamp """ if suspended_until: self.suspended_until = datetime.utcfromtimestamp(suspended_until) else: self.suspended_until = None self.last_message = message self.save() if suspended_until: # We make the `suspended_until` key auto expire key = self.key('suspended_until') connection.expire(key, round(suspended_until - now_timestamp()) + 1)
class DeviceDynamicInterface(models.Model): '''''' dev_id = models.Attribute(required=True) timestamp = models.DateTimeField(required=True) dynamic_info = models.ListField(DeviceInfoInterface, required=False)