class EditAdminForm(Form): newpassword = f.PasswordField( "登录密码", description="不修改密码请留空", validators=[validators.Optional(), validators.Length(5, 32)]) name = f.StringField("名字", validators=[validators.DataRequired(message="不能为空")]) mobile = f.StringField("手机", validators=[validators.Optional()]) email = f.StringField( "电子邮箱", validators=[validators.Optional(), validators.Email(message="格式不正确")]) is_super = f.BooleanField("超级管理员") roles = f.SelectMultipleField("权限", choices=Admin.ROLES) state = f.SelectField("是否启用", choices=[ ("1", "启用"), ("0", "禁用"), ]) manage_provinces = f.SelectMultipleField("管辖省份", choices=ChinaCity.get_provinces()) def validate_mobile(self, field): if not is_mobile(field.data): raise ValidationError('手机号码格式不正确')
def get(self): team = self.current_team if team.sport: team.sport = [s for s in Sport.select().where(Sport.id << team.sport)] form = TeamBasicFrom(obj=team) province = self.current_team.province if province: form.city.choices = ChinaCity.get_cities(province) self.render("settings/basic.html", form=form, cities=ChinaCity.get_cities() )
def __init__(self, *args, **kwargs): super(CreateActivityFrom, self).__init__(*args, **kwargs) obj = kwargs.get("obj", None) team = kwargs.get("team", None) if not isinstance(team, Team): raise AssertionError("must a team") if obj and obj.province: province = obj.province else: province = self.province.choices[0][0] if province: self.city.choices = ChinaCity.get_cities(province) leaders = team.get_members(role="leader") leaders.insert(0, User.get_or_none(id=team.owner_id)) if leaders: self.leader.choices = [(str(user.id), user.name or user.mobile) for user in leaders] groups = team.groups if groups: self.allow_groups.choices = [(str(group.id), group.name) for group in groups]
def get(self): team = self.current_team if team is not None: if team.state == 1: return self.redirect(self.reverse_url("club_home")) else: return self.redirect(self.reverse_url("club_wait_approve")) form = CreateTeamFrom() province = form.province.choices[0][0] if province: form.city.choices = ChinaCity.get_cities(province) self.render("club/create.html", form=form, cities=ChinaCity.get_cities())
def get(self, activity_id): activity = Activity.get_or_404(id=activity_id) form = CreateActivityFrom(obj=activity, team=self.current_team) self.render("activity/edit.html", form=form, cities=ChinaCity.get_cities())
def post(self): form = CreateActivityFrom(self.arguments, team=self.current_team) if form.validate(): activity = Activity() form.populate_obj(activity) need_fields = self.get_arguments("need_fields") for field in need_fields: setattr(activity, field, True) geocode = yield self.get_geocode(activity.city, activity.address) if geocode.get("geocodes", []): location = geocode['geocodes'][0]['location'].split(",") activity.lat = location[1] activity.lng = location[0] activity.geohash = geohash.encode(float(location[1]), float(location[0])) if activity.repeat_type == "week": activity.week_day = activity.start_time.weekday() + 1 elif activity.repeat_type == "month": activity.month_day = activity.start_time.day activity.team = self.current_team activity.creator = self.current_user activity.save() # 更新俱乐部活动数量 Team.update_activities_count(self.current_team.id) self.redirect(self.reverse_url("club_activity_list")) return province = self.get_argument("province", None) if province: form.city.choices = ChinaCity.get_cities(province) self.render("activity/new.html", form=form, cities=ChinaCity.get_cities())
class CreateTeamFrom(Form): type = f.SelectField("类型", default="1", choices=[ ("1", "赛事主办"), ("0", "俱乐部"), ]) name = f.StringField("名称", description="", validators=[ validators.DataRequired(message="必填"), validators.Length(2, 64) ]) iconfile = FileField( "微标", description="仅支持格式:jpg、png, 不能超过10M", validators=[ # file_required(message="必填"), file_allowed(("jpg", "png", 'jpeg'), message="仅支持格式:jpg、png") ]) sport = ModelSelectMultipleField("运动类型", model=Sport, get_label="name") province = f.SelectField("省份", validators=[ validators.DataRequired(message="必填"), ], choices=ChinaCity.get_provinces()) city = WPSelectField("城市", validators=[ validators.DataRequired(message="必填"), ], choices=[]) description = f.TextAreaField("介绍", description="", validators=[ validators.DataRequired(message="必填"), validators.Length(5, 2000) ]) open_type = f.SelectField( "加入验证", choices=[ ("0", "允许任何人加入"), ("1", "需要验证"), # ("2", "交会费加入"), ("3", "不允许任何人加入") ])
def post(self): team = self.current_team if team is None: team = Team(owner_id=self.current_user.id) form = CreateTeamFrom(self.arguments) if form.validate(): form.populate_obj(team) team.owner_id = self.current_user.id team.sport = [str(n.id) for n in team.sport] if "iconfile" in self.request.files: to_bucket = self.settings['qiniu_avatar_bucket'] to_key = "team:%s%s" % (self.current_user.id, time.time()) to_key = hashlib.md5(to_key.encode()).hexdigest() icon_key = self.upload_file( "iconfile", to_bucket=to_bucket, to_key=to_key, ) team.icon_key = icon_key team.save() # TODO: 正式版本需要移除此自动通过审核功能 tasks.team.approve_team.apply_async((team.id, ), countdown=30) return self.redirect(self.reverse_url("club_wait_approve")) province = self.get_argument("province", None) if province: form.city.choices = ChinaCity.get_cities(province) self.render("club/create.html", form=form, cities=ChinaCity.get_cities())
def post(self): form = TeamBasicFrom(self.arguments) if form.validate(): team = self.current_team form.populate_obj(team) team.sport = [str(s.id) for s in team.sport] if "iconfile" in self.request.files: to_bucket = self.settings['qiniu_avatar_bucket'] to_key = "team:%s%s" % (self.current_user.id, time.time()) to_key = hashlib.md5(to_key.encode()).hexdigest() icon_key = self.upload_file("iconfile", to_bucket=to_bucket, to_key=to_key, ) if icon_key: team.icon_key = icon_key team.save() self.flash("修改俱乐部资料成功!", category='success') self.redirect(self.reverse_url("club_settings_basic")) return province = self.current_team.province if province: form.city.choices = ChinaCity.get_cities(province) self.render("settings/basic.html", form=form, cities=ChinaCity.get_cities() )
def __init__(self, *args, **kwargs): super(CreateMatchFrom, self).__init__(*args, **kwargs) obj = kwargs.get("obj", None) team = kwargs.get("team", None) if not isinstance(team, Team): raise AssertionError("must a team") if obj and obj.province: province = obj.province else: province = self.province.choices[0][0] if province: self.city.choices = ChinaCity.get_cities(province)
class RegisterForm(Form): email = f.StringField("邮箱", validators=[ validators.DataRequired(message="必填"), validators.Email(message="必须是电子邮箱") ]) captcha = f.StringField("图形验证码", validators=[validators.DataRequired(message="必填")]) name = f.StringField("昵称", validators=[ validators.DataRequired(message="必填"), validators.Length(1, 40, message="长度不符合要求") ]) password = f.PasswordField("登录密码", validators=[ validators.DataRequired(message="必填"), validators.Length(5, 32, message="长度不符合要求"), validators.EqualTo('confirmed_password', message="密码需要和确认密码相同") ]) province = f.SelectField("省份", validators=[ validators.DataRequired(message="必填"), ], choices=ChinaCity.get_provinces()) city = WPSelectField("城市", validators=[ validators.DataRequired(message="必填"), ], choices=[]) confirmed_password = f.PasswordField( "确认密码", validators=[ validators.DataRequired(message="必填"), validators.Length(5, 32, message="长度不符合要求") ])
def get(self): match = Match(team_id=self.current_team.id, contact_person=self.current_team.contact_person, contact_phone=self.current_team.contact_phone, province=self.current_team.province, city=self.current_team.city, address=self.current_team.address) form = CreateMatchFrom(obj=match, team=self.current_team) self.render("match/create.html", match=match, form=form, cities=ChinaCity.get_cities(), groups=[], group_type=0, options=[], custom_options=[])
def get(self, match_id): match = Match.get_or_404(id=match_id) match.sport_id = Sport.get_or_none(id=match.sport_id) match.group_type = str(match.group_type) team = Team.get_or_404(id=match.team_id) form = EditMatchFrom(obj=match, team=team) # 获取赛事分组信息 query = MatchGroup.select().where( MatchGroup.match_id == match.id).order_by( MatchGroup.sort_num.desc()) groups = [] for group in query: group = group.info group['max'] = group['max_members'] groups.append(group) # 获取报名表自定义选项 query = MatchOption.select().where( MatchOption.match_id == match.id).order_by( MatchOption.sort_num.desc()) custom_options = [] for option in query: option = option.info if 'choices' in option: option['choices'] = "|".join(option['choices']) custom_options.append(option) self.render("match/edit.html", form=form, match=match, cities=ChinaCity.get_cities(), group_type=match.group_type, groups=groups, custom_options=custom_options, options=match.fields)
def get(self): duplicate_id = self.get_argument("duplicate_id", None) if duplicate_id: activity = Activity.get_or_none(id=duplicate_id) activity.id = None else: activity = Activity( team=self.current_team, contact_person=self.current_team.contact_person, contact_phone=self.current_team.contact_phone, province=self.current_team.province, city=self.current_team.city, address=self.current_team.address ) form = CreateActivityFrom(obj=activity, team=self.current_team) self.render("activity/new.html", form=form, cities=ChinaCity.get_cities())
class CreateAdminForm(Form): username = f.StringField("用户名", validators=[ validators.DataRequired(message="不能为空"), validators.Length(3, 32) ]) password = f.PasswordField("密码", validators=[ validators.DataRequired(message="不能为空"), validators.Length(5, 32) ]) name = f.StringField("名字", validators=[validators.DataRequired(message="不能为空")]) mobile = f.StringField("手机", validators=[validators.Optional()]) email = f.StringField( "电子邮箱", validators=[validators.Optional(), validators.Email(message="格式不正确")]) is_super = f.BooleanField("超级管理员") roles = f.SelectMultipleField("权限", choices=Admin.ROLES) state = f.SelectField("是否启用", choices=[ ("1", "启用"), ("0", "禁用"), ]) manage_provinces = f.SelectMultipleField("管辖省份", choices=ChinaCity.get_provinces()) def validate_username(self, field): if Admin.select().where(Admin.username == field.data).count() > 0: raise ValidationError('用户名已存在') def validate_mobile(self, field): if not is_mobile(field.data): raise ValidationError('手机号码格式不正确')
class CreateMatchFrom(Form): def __init__(self, *args, **kwargs): super(CreateMatchFrom, self).__init__(*args, **kwargs) obj = kwargs.get("obj", None) team = kwargs.get("team", None) if not isinstance(team, Team): raise AssertionError("must a team") if obj and obj.province: province = obj.province else: province = self.province.choices[0][0] if province: self.city.choices = ChinaCity.get_cities(province) title = f.StringField("比赛名称", description="", validators=[ validators.DataRequired(message="必填"), validators.Length(1, 200, message="不能超过200字") ]) coverfile = FileField("封面", description="建议尺寸:1045x464,仅支持格式:jpg、png, 不能超过10M", validators=[ file_required(message="必填"), file_allowed(("jpg", "png", "jpeg"), message="仅支持格式:jpg、png") ]) sport_id = ModelSelectField("运动类型", model=Sport, get_label="name") type = f.SelectField("比赛类型", description="对战型如:足球、蓝球,非对战型:跑步、自行车", validators=[ validators.DataRequired(message="必填"), ], choices=(('0', "对战型"), ('1', "非对战型"))) province = f.SelectField("省份", validators=[ validators.DataRequired(message="必填"), ], choices=ChinaCity.get_provinces()) city = WPSelectField("城市", validators=[ validators.DataRequired(message="必填"), ], choices=[]) address = f.StringField("详细地址", description="非场地运动新填写集合地点", validators=[ validators.DataRequired(message="必填"), ]) lat = f.HiddenField("lat") lng = f.HiddenField("lng") formatted_address = f.HiddenField("address_name") start_time = f.DateTimeField( "开始时间", description="赛事开始时间", validators=[validators.DataRequired(message="必填")]) end_time = f.DateTimeField("结束时间", validators=[ validators.DataRequired(message="必填"), LaterThan("start_time", message="必须晚于开始时间") ]) join_start = f.DateTimeField("报名开始时间", description="限时开始报名,不填赛事上线即可报名", validators=[validators.Optional()]) join_end = f.DateTimeField("报名截止时间", description="不填则开始前均可报名", validators=[ validators.Optional(), BeforeThan("start_time", message="必须早于开始时间"), LaterThan("join_start", message="必须晚于报名开始时间") ]) contact_person = f.StringField("联系人", description="", validators=[ validators.DataRequired(message="必填"), validators.Length(1, 200, message="不能超过200字") ]) contact_phone = f.StringField("联系电话", description="", validators=[ validators.DataRequired(message="必填"), validators.Length(1, 200, message="不能超过200字") ]) description = f.TextAreaField( "简介", description="", validators=[validators.DataRequired(message="必填")]) rules = f.TextAreaField("规程", description="", validators=[validators.DataRequired(message="必填")]) reward = f.StringField("奖励", description="奖励说明,如:冠军1000元,亚军500元", validators=[ validators.Optional(), validators.Length(0, 200, message="不能超过200字") ]) join_type = f.SelectField("报名类型", validators=[ validators.DataRequired(message="必填"), ], choices=(('0', "个人"), ('1', "团队"))) refund_type = f.SelectField("退款策略", validators=[ validators.DataRequired(message="必填"), ], choices=(('0', "开始前可以退款"), ('1', "报名截止前可退"), ('2', "不能退款"))) max_members = f.IntegerField("人数或团队限制", description="比赛人数或团队限制,报满则无法继续报名", default=15, validators=[ validators.Optional(), validators.NumberRange( 0, 20000, message="人数限制必须在2到20000人之间") ]) price = f.DecimalField("报名费(元)", description="设置分组后将以分组报名费为准", validators=[validators.Optional()]) group_type = f.SelectField("分组模式", validators=[ validators.DataRequired(message="必填"), ], choices=(('0', "非分组比赛"), ('1', "分组比赛"))) groups = f.StringField("分组", validators=[validators.Optional()])
class Match(BaseModel): """ 赛事 """ class Meta: db_table = 'match' order_by = ('-id', ) indexes = ((('lat', 'lng'), False), ) # 赛事状态可选值 class MatchState(IntEnum): closed = -1 cancelled = 0 wait_review = 5 rejected = 10 in_review = 15 opening = 20 finished = 100 STATES = { MatchState.closed: "已关闭", MatchState.cancelled: "已取消", MatchState.wait_review: "等待审核", MatchState.rejected: "审核拒绝", MatchState.in_review: "正在审核", MatchState.opening: "进行中", MatchState.finished: "已结束" } REFUND_TYPES = { 0: "开始前可以退款", 1: "报名截止前可退", 2: "不能退款", } team_id = IntegerField(index=True) user_id = IntegerField(index=True) type = IntegerField(default=0, verbose_name="赛事类型", help_text="对战型如:足球、蓝球,非对战型:跑步、自行车", choices=((0, "对战型"), (1, "非对战型"))) title = CharField(max_length=180, index=True, verbose_name="标题") cover_key = CharField(null=True, verbose_name="海报") sport_id = IntegerField() score_unit = CharField(default="", verbose_name="成绩单位") contact_person = CharField(default="", max_length=120, verbose_name="联系人") contact_phone = CharField(default="", max_length=120, verbose_name="联系电话") contact = TextField(default="", verbose_name="联系方式") description = TextField(null=False, verbose_name="描述") rules = TextField(default="", verbose_name="赛事规程") reward = CharField(default="", verbose_name="奖励说明") country = CharField(default="中国", max_length=128) province = CharField(default="", max_length=128, choices=ChinaCity.get_provinces(), verbose_name="省份") city = CharField(default="", max_length=128, index=True, verbose_name="城市", choices=[]) address = CharField(default="", max_length=250, verbose_name="举办地址") lat = DoubleField(default=0) lng = DoubleField(default=0) location = PointField(null=True) geohash = CharField(default="", index=True, max_length=16) # gym_id = IntegerField(default=0, verbose_name="场馆ID") join_type = IntegerField(default=0, choices=((0, "个人"), (1, "团队"))) max_members = IntegerField(default=0, verbose_name="人数限制") members_count = IntegerField(default=0) group_type = IntegerField(default=0, choices=((0, "不分"), (1, "分"))) recommend_time = IntegerField(default=0, verbose_name="推荐时间") recommend_region = IntegerField(default=0, verbose_name="推荐范围", choices=((0, "全国"), (1, "同城"))) payment_type = IntegerField(default=0, verbose_name="支付方式", help_text="0 在线支付 1 线下支付 2 均可") start_time = DateTimeField(null=True, verbose_name="开始时间") end_time = DateTimeField(null=True, verbose_name="结束时间") duration = IntegerField(default=0, verbose_name="活动持续时间", help_text="自动根据开始和结束时间计算") join_start = DateTimeField(null=True, verbose_name="报名开始时间") join_end = DateTimeField(null=True, verbose_name="报名截止时间") cancelled = DateTimeField(null=True, verbose_name="取消时间") cancel_reason = TextField(default="", verbose_name="取消原因") reject_time = DateTimeField(null=True, verbose_name="") reject_reason = TextField(default="") verified = BooleanField(default=False, index=True) verify_reason = CharField(default="", max_length=250) price = DecimalField(default=Decimal(0), decimal_places=2, verbose_name="报名费") refund_type = IntegerField(default=1, verbose_name="退款策略", choices=((0, "开始前可以退款"), (1, "报名截止前可退"), (2, "不能退款"))) created = DateTimeField(default=datetime.now, verbose_name="创建时间") updated = DateTimeField(default=datetime.now, verbose_name="最后更新") finished = DateTimeField(null=True, verbose_name="结算时间") fields = ListField(default=[], verbose_name="报名表系统选项") # 已经上线的活动被修改的时候 会被复制一份 # 在被复制的活动审核通过的时候 被修改的内容会被更新到 原活动 wait_review_for_match = IntegerField(default=0, verbose_name="原活动") state = IntegerField(default=MatchState.wait_review, verbose_name="赛事状态") pushed = DateTimeField(null=True, help_text="赛事开始通知是否推送") @cached_property def join_type_name(self): if self.join_type == 0: return "个人" return "团队" @cached_property def group_type_name(self): if self.group_type == 0: return "不分组" return "分组" @cached_property def refund_type_name(self): return self.REFUND_TYPES.get(self.refund_type, "未知") @cached_property def refund_expire(self): if self.refund_type == 0: return self.start_time elif self.refund_type == 1: return self.join_end or self.start_time return None @cached_property def sport_name(self): if not self.sport_id: return "" sport = Sport.get_or_none(id=self.sport_id) return sport.name if sport else "" @cached_property def sport(self): if not self.sport_id: return None return Sport.get_or_none(id=self.sport_id) @property def field_names(self): names = [] for field in self.fields: names.append(MatchOption.get_builtin_field_name(field)) return names def query_custom_options(self): """ 查询自定义选项,返回查询对象Query """ # 获取自定义选项 query = MatchOption.select().where( MatchOption.match_id == self.id).order_by( MatchOption.sort_num.desc()) return query @cached_property def custom_options_list(self): """ 获取赛事报名自定义选项列表 """ options = [] for option in self.query_custom_options(): assert isinstance(option, MatchOption) options.append(option.info) return options @cached_property def option_info_list(self): """ 获取选项信息列表 """ class OptionInfoDisplayObject(object): def __init__(self, option_type: Union[MatchOption.BuiltinFieldTypes, MatchOption.CustomFieldTypes], option_key: str, option_name: str): self.option_type = option_type self.option_key = option_key self.option_name = option_name def get_option_value(self, source): return source.get_option_value(self.option_type, self.option_key) def is_avatar(self): if self.option_type is MatchOption.BuiltinFieldTypes.Avatar: return True return False def is_idcard_photo(self): if self.option_type is MatchOption.BuiltinFieldTypes.IdcardPhoto: return True return False def is_leader_check(self): if self.option_type is MatchOption.BuiltinFieldTypes.IsLeader: return True return False def is_gender(self): if self.option_type is MatchOption.BuiltinFieldTypes.Gender: return True return False def is_photo(self): if self.option_type is MatchOption.CustomFieldTypes.Photo: return True return False def is_file(self): if self.option_type is MatchOption.CustomFieldTypes.File: return True return False def is_boolean(self): if self.option_type is MatchOption.CustomFieldTypes.Boolean: return True return False def __repr__(self): return "OptionInfoDisplayObject(option_type:(%s), " \ "option_key:(%s), option_name:(%s))" \ % (self.option_type, self.option_key, self.option_name) option_list = deque() # 获取内置选项 for field in self.fields: item = OptionInfoDisplayObject( MatchOption.BuiltinFieldTypes(field), field, MatchOption.get_builtin_field_name(field), ) if MatchOption.judge_builtin_field( field, MatchOption.BuiltinFieldTypes.Avatar): option_list.appendleft(item) else: option_list.append(item) # 获取自定义选项 for option in self.query_custom_options(): assert isinstance(option, MatchOption) custom_item = OptionInfoDisplayObject( MatchOption.CustomFieldTypes(option.field_type), str(option.id), str(option.title)) option_list.append(custom_item) return option_list def is_wait_review(self): return self.state == self.MatchState.wait_review def can_change_goups(self): """ 上架前的赛事可以修改分组列表 """ return self.state in (self.MatchState.wait_review, self.MatchState.rejected) def can_change(self): """ 结算、取消和关闭的赛事不能再修改 """ return self.state not in (self.MatchState.finished, self.MatchState.cancelled, self.MatchState.closed) def can_cancel(self): """ 结束前可以取消 """ if self.MatchState.cancelled.value < self.state <= \ self.MatchState.opening.value: return True return False def can_leave(self) -> bool: """ 进行中的赛事可以退出 :return: bool """ if self.state != self.MatchState.opening.value: logging.debug("不能退赛, 赛事状态为: {0}".format(self.state)) return False now = datetime.now() # 赛事开始前可退出 if self.refund_type == 0 and self.start_time > now: return True # 报名截至前可退出 elif self.refund_type == 1 and self.join_end > now: return True else: return False @cached_property def open_for_join(self): if self.MatchState != self.MatchState.opening: return False if self.members_count >= self.max_members: return False if datetime.now() >= self.join_end: return False if datetime.now() < self.join_start: return False return True @cached_property def is_finished(self): return datetime.now() >= self.end_time @cached_property def is_join_end(self): return datetime.now() >= self.join_end def can_apply_settlement(self): if self.is_join_end: return True else: return False @cached_property def state_name(self): if self.state != self.MatchState.opening: return self.STATES.get(self.state, "未知") time_now = datetime.now() if self.join_start and time_now < self.join_start: return "等待报名开始" if self.start_time < time_now < self.end_time: return "进行中" if time_now <= self.join_end: return "报名中" if self.join_end < time_now < self.start_time: return "报名截止" if time_now >= self.end_time: return "已结束" if self.members_count >= self.max_members and \ time_now < self.join_end: return "已报满" return "未知" def can_join(self): if self.state == self.MatchState.finished: return {"can": False, "reason": "赛事已结束"} elif self.state == self.MatchState.cancelled: return {"can": False, "reason": "赛事已取消"} elif self.state != self.MatchState.opening: return {"can": False, "reason": "赛事尚未通过审核"} elif self.join_start and self.join_start > datetime.now(): return {"can": False, "reason": "报名未开始"} elif (self.join_end and self.join_end < datetime.now()) or ( not self.join_end and self.start_time < datetime.now()): return {"can": False, "reason": "报名已截止"} elif self.members_count >= self.max_members: return {"can": False, "reason": "已报满"} return {"can": True, "reason": ""} @property def cover(self): cover_url = app.settings['avatar_url'].rstrip('/') return Match.get_cover_urls(self.cover_key, cover_url, crop=False) @property def icon(self): cover_url = app.settings['avatar_url'].rstrip('/') return Match.get_cover_urls(self.cover_key, cover_url, crop=True) @cached_property def list_info(self): info = self.to_dict(exclude=[ Match.geohash, Match.cover_key, Match.description, Match.rules, Match.fields, Match.contact, Match.contact, Match.duration, Match.location, Match.wait_review_for_match ]) info['cover'] = self.cover info['icon'] = self.icon info['open_for_join'] = self.open_for_join info['state_name'] = self.state_name return info @classmethod def update_members_count(cls, match_id): Match.update(members_count=MatchMember.select().where( MatchMember.match_id == match_id, MatchMember.state == MatchMember.MatchMemberState.normal).count()).where( Match.id == match_id).execute() def leave_request(self, member): """申请退赛""" MatchMember.update(state=MatchMember.MatchMemberState.leave.value)\ .where(MatchMember.id == member.id).execute() def leave(self, member): """ 执行退出赛事操作 :param insists: :param member: MatchMember instance :return: """ with Match._meta.database.transaction(): member.delete_instance() self.update_members_count(match_id=self.id) if member.group_id: MatchGroup.update_members_count(group_id=member.group_id)
class CreateActivityFrom(Form): def __init__(self, *args, **kwargs): super(CreateActivityFrom, self).__init__(*args, **kwargs) obj = kwargs.get("obj", None) team = kwargs.get("team", None) if not isinstance(team, Team): raise AssertionError("must a team") if obj and obj.province: province = obj.province else: province = self.province.choices[0][0] if province: self.city.choices = ChinaCity.get_cities(province) leaders = team.get_members(role="leader") leaders.insert(0, User.get_or_none(id=team.owner_id)) if leaders: self.leader.choices = [(str(user.id), user.name or user.mobile) for user in leaders] groups = team.groups if groups: self.allow_groups.choices = [(str(group.id), group.name) for group in groups] title = f.StringField("活动标题", description="", validators=[ validators.DataRequired(message="必填"), validators.Length(2, 32) ]) cover = FileField("活动图片", description="仅支持格式:jpg、png, 不能超过2M", validators=[ file_allowed(("jpg", "png", "jpeg"), message="仅支持格式:jpg、png") ]) sport = ModelSelectField("运动类型", model=Sport, get_label="name") province = f.SelectField("省份", validators=[ validators.DataRequired(message="必填"), ], choices=ChinaCity.get_provinces()) city = WPSelectField("城市", validators=[ validators.DataRequired(message="必填"), ], choices=[]) address = f.StringField("详细地址", description="非场地运动新填写集合地点", validators=[ validators.DataRequired(message="必填"), ]) lat = f.HiddenField("lat") lng = f.HiddenField("lng") formatted_address = f.HiddenField("address_name") start_time = f.DateTimeField( "开始时间", description="首次活动开始时间", validators=[validators.DataRequired(message="必填")]) end_time = f.DateTimeField("结束时间", validators=[ validators.DataRequired(message="必填"), LaterThan("start_time", message="必须晚于活动开始时间") ]) repeat_type = f.SelectField( "循环活动", description="活动结算后系统自动生成下期活动", choices=[ ("", "不循环"), # ("day", "每天"), ("week", "每周"), # ("month", "每月") ]) repeat_end = f.DateTimeField("结束循环", description="超过此时间活动将不能自动循环", validators=[ validators.Optional(), LaterThan("start_time", message="必须晚于活动开始时间") ]) join_end = f.DateTimeField("报名截止时间", description="", validators=[validators.Optional()]) description = f.TextAreaField("活动说明", description="", validators=[ validators.DataRequired(message="必填"), validators.Length(1, 5000, message="不能超过5000字") ]) max_members = f.IntegerField("人数限制", description="活动限制人数", default=15, validators=[ validators.Optional(), validators.NumberRange( 2, 20000, message="人数限制必须在2到20000人之间") ]) allow_agents = f.IntegerField("允许代报人数", description="代报人数不包含报名人自己", default=0, validators=[ validators.Optional(), validators.NumberRange( 0, 200, message="允许代报人数限制必须在0到200人之间") ]) payment_type = f.SelectField( "支付方式", default="", choices=[("0", "在线支付"), ("1", "线下支付")], validators=[validators.DataRequired(message="必填")]) price = f.DecimalField("价格(人/次)", description="") # female_price = f.DecimalField("女成员价格") vip_price = f.DecimalField("VIP价格(人/次)", description="VIP会员专享价格") allow_free_times = f.BooleanField("允许使用次卡") visible = f.SelectField("允许报名", default="", choices=[("0", "所有人"), ("1", "仅成员")], validators=[validators.Optional()]) allow_groups = f.SelectMultipleField("允许分组", default="", description="例如:限制只能高级组会员可以报名,默认不限", choices=[], validators=[validators.Optional()]) leader = f.SelectField("组织者", choices=[]) need_fields = f.SelectMultipleField( "需要填写的报名信息", description="您希望报名人员填写哪些信息", default=['need_nickname', 'need_gender', 'need_mobile'], choices=[("need_nickname", "昵称"), ("need_mobile", "手机"), ("need_gender", "性别"), ("need_name", "姓名"), ("need_identification", "身份证明"), ("need_emergency_contact", "紧急联系人")]) need_ext1_name = f.StringField("信息名称", description="不需要请留空") need_ext1_type = f.SelectField("类型", choices=(('text', '文本'), ('photo', '照片'))) need_ext2_name = f.StringField("信息名称") need_ext2_type = f.SelectField("类型", choices=(('text', '文本'), ('photo', '照片'))) need_ext3_name = f.StringField("信息名称") need_ext3_type = f.SelectField("类型", choices=(('text', '文本'), ('photo', '照片')))
def post(self, match_id): match = Match.get_or_404(id=match_id) team = Team.get_or_404(id=match.team_id) form = EditMatchFrom(self.arguments, team=team) groups = self.parse_groups() options = self.parse_options() custom_options = self.parse_custom_options() # 验证分组设置 groups_validated = self.validate_groups(form, groups) if form.validate() and groups_validated: with (self.db.transaction()): form.populate_obj(match) # 计算赛事总人数限制 if intval(match.group_type) == 1: match.price = min(map(lambda x: float(x['price']), groups)) if groups else 0 match.max_members = reduce(lambda x, y: x + y, map(lambda x: x['max'], groups)) if groups else 0 if "coverfile" in self.request.files: to_bucket = self.settings['qiniu_avatar_bucket'] to_key = "match:%s%s" % (self.current_user.id, time.time()) to_key = hashlib.md5(to_key.encode()).hexdigest() cover_key = self.upload_file( "coverfile", to_bucket=to_bucket, to_key=to_key, ) match.cover_key = cover_key match.user_id = self.current_user.id match.fields = options if not match.join_end: match.join_end = match.start_time match.save() if intval(match_id) > 0: group_ids = [ group['id'] for group in groups if group['id'] > 0 ] if len(group_ids) > 0: MatchGroup.delete().where( MatchGroup.match_id == intval(match_id), MatchGroup.id.not_in(group_ids)).execute() else: MatchGroup.delete().where( MatchGroup.match_id == intval(match_id)).execute() # 保存分组 for group in groups: if group['id'] > 0: MatchGroup.update( name=group['name'], price=group['price'], max_members=group['max']).where( MatchGroup.id == group['id']).execute() else: MatchGroup.create(match_id=match.id, name=group['name'], price=group['price'], max_members=group['max']) if intval(match_id) > 0: custom_option_ids = [ custom_option['id'] for custom_option in custom_options if custom_option['id'] > 0 ] if len(custom_option_ids) > 0: MatchOption.delete().where( MatchOption.match_id == intval(match_id), MatchOption.id.not_in( custom_option_ids)).execute() else: MatchOption.delete().where(MatchOption.match_id == intval(match_id)).execute() # 保存自定义选项 for custom_option in custom_options: if custom_option['id'] > 0: MatchOption.update( title=custom_option['title'], field_type=custom_option['field_type'], required=custom_option['required'], choices=custom_option['choices'], ).where( MatchOption.id == custom_option['id']).execute() else: MatchOption.create( match_id=match.id, title=custom_option['title'], field_type=custom_option['field_type'], required=custom_option['required'], choices=custom_option['choices'], ) # MatchService.add_match_start_notify(match) self.redirect(self.reverse_url("admin_match_detail", match.id)) return province = self.get_argument("province", None) if province: form.city.choices = ChinaCity.get_cities(province) self.validate_groups(form, groups) self.render("match/edit.html", form=form, match=match, cities=ChinaCity.get_cities(), groups=groups, group_type=self.get_argument("group_type", "0"), options=options, custom_options=custom_options)
class TeamBasicFrom(Form): name = f.StringField("名称", description="俱乐部或赛事主办机构名称", validators=[ validators.DataRequired(message="必填项"), validators.Length(2, 64) ]) iconfile = FileField("徽标", description="仅支持格式:jpg、png, 不能超过10M", validators=[ file_allowed(("jpg", "png", "jpeg"), message="仅支持格式:jpg、png") ]) sport = ModelSelectMultipleField("运动类型", model=Sport, get_label="name" ) province = f.SelectField("省份", validators=[ validators.DataRequired(message="必填项"), ], choices=ChinaCity.get_provinces()) city = WPSelectField("城市", validators=[ validators.DataRequired(message="必填项"), ], choices=[]) address = f.StringField("联系地址", description="", validators=[ validators.Optional(), validators.Length(3, 128) ]) lat = f.HiddenField("lat") lng = f.HiddenField("lng") contact_person = f.StringField("联系人", description="", validators=[ validators.Optional(), validators.Length(2, 32) ]) contact_phone = f.StringField("联系电话", description="", validators=[ validators.Optional(), validators.Length(3, 32) ]) description = f.TextAreaField("介绍", description="", validators=[ validators.DataRequired(message="必填"), validators.Length(5, 2000) ])
def get(self): self.write(ChinaCity.get_cities())
class Activity(BaseModel): class PaymentType(IntEnum): ONLINE_PAY = 0 CASH_PAY = 1 # 活动状态可选值 class ActivityState(IntEnum): closed = -1 cancelled = 0 opening = 1 finished = 2 STATES = { ActivityState.closed: "已关闭", ActivityState.cancelled: "已取消", ActivityState.opening: "进行中", ActivityState.finished: "已结束" } team = ForeignKeyField(Team, related_name="team_activities", verbose_name="所属俱乐部") creator = ForeignKeyField(User, related_name="user_created_activities", verbose_name="创建人") title = CharField(max_length=180, null=False, index=True, verbose_name="标题") type = IntegerField(default=0, verbose_name="活动类型", choices=((0, "活动"), (1, "比赛"))) sport = ForeignKeyField(Sport, null=True, verbose_name="运动类型") leader = ForeignKeyField(User, null=True, related_name="user_leader_activities", verbose_name="组织者") contact_person = CharField(default="", max_length=250, verbose_name="联系人") contact_phone = CharField(default="", max_length=250, verbose_name="联系电话") description = TextField(null=False, verbose_name="描述") country = CharField(default="中国", max_length=128) province = CharField(default="", max_length=128, choices=ChinaCity.get_provinces(), verbose_name="省份") city = CharField(default="", max_length=128, index=True, verbose_name="城市", choices=[]) address = CharField(default="", max_length=250, verbose_name="活动地址") # 活动当前坐标,通过地址获取 lat = DoubleField(default=0) lng = DoubleField(default=0) location = PointField(null=True) geohash = CharField(default="", index=True, max_length=16) gym_id = IntegerField(default=0, verbose_name="场馆ID") min_members = IntegerField(default=0) max_members = IntegerField(default=0, verbose_name="人数限制") public_memebers = BooleanField(default=True, verbose_name="公开报名人列表") members_count = IntegerField(default=0) comments_count = IntegerField(default=0) recommend_time = IntegerField(default=0, verbose_name="推荐时间") recommend_region = IntegerField(default=0, verbose_name="推荐范围", help_text=" # 0 全国 1 同城") payment_type = IntegerField(default=0, verbose_name="支付方式", help_text="0 在线支付 1 线下支付, 不允许修改") allow_free_times = BooleanField(default=False, verbose_name="允许使用次卡") # 活动针对的组别列表 默认为空 代表针对全部用户 allow_groups = ListField(default="", null=True, verbose_name="分组限制", choices=[]) allow_agents = IntegerField(default=0, verbose_name="允许代报人数") start_time = DateTimeField(null=True, verbose_name="开始时间") end_time = DateTimeField(null=True, verbose_name="结束时间") duration = IntegerField(default=0, verbose_name="活动持续时间", help_text="自动根据开始和结束时间计算") repeat_type = CharField(default="", verbose_name="重复类型") repeat_end = DateTimeField(null=True, verbose_name="结束重复") month_day = IntegerField(default=0, help_text="每月几日循环") week_day = IntegerField(default=0, help_text="每周几循环") join_start = DateTimeField(null=True, verbose_name="报名开始时间") join_end = DateTimeField(null=True, verbose_name="报名截止时间") cancelled = DateTimeField(null=True, verbose_name="取消时间") cancel_reason = TextField(default="", verbose_name="取消原因") verified = BooleanField(default=False, index=True) verify_reason = CharField(default="", max_length=250) price = DecimalField(default=Decimal(0), decimal_places=2, verbose_name="价格") female_price = DecimalField(default=Decimal(0), decimal_places=2, verbose_name="女生价格") vip_price = DecimalField(default=Decimal(0), decimal_places=2, verbose_name="VIP价格") join_level_discount = BooleanField(default=True, verbose_name="参加会员折扣") # 报名人员填写信息 need_nickname = BooleanField(default=False, verbose_name="需要昵称") need_mobile = BooleanField(default=False, verbose_name="需要手机") need_gender = BooleanField(default=False, verbose_name="需要性别") need_name = BooleanField(default=False, verbose_name="需要姓名") need_identification = BooleanField(default=False, verbose_name="需要身份证") need_emergency_contact = BooleanField(default=False, verbose_name="需要紧急联系人") need_gps = BooleanField(default=False, verbose_name="是否实时地理位置") need_ext1_name = CharField(null=True, verbose_name="扩展属性1名称") need_ext1_type = CharField(null=True, verbose_name="扩展属性1类型", choices=(('text', '文本'), ('photo', '照片'))) need_ext2_name = CharField(null=True, verbose_name="扩展属性2名称") need_ext2_type = CharField(null=True, verbose_name="扩展属性2类型", choices=(('text', '文本'), ('photo', '照片'))) need_ext3_name = CharField(null=True, verbose_name="扩展属性3名称") need_ext3_type = CharField(null=True, verbose_name="扩展属性3类型", choices=(('text', '文本'), ('photo', '照片'))) visible = IntegerField(default=0, index=True, verbose_name="可见性", choices=((0, "所有人"), (1, "仅成员"))) refund_type = IntegerField(default=1, verbose_name="退款策略", choices=((0, "开始前可以退款"), (1, "报名截止前可退"), (2, "不能退款"))) created = DateTimeField(default=datetime.now, verbose_name="创建时间") updated = DateTimeField(default=datetime.now, verbose_name="最后更新") finished = DateTimeField(null=True, verbose_name="结算时间") online_paid_amount = DecimalField(default=Decimal(0), verbose_name="在线支付收入") credit_paid_amount = DecimalField(default=Decimal(0), verbose_name="余额支付收入") cash_paid_amount = DecimalField(default=Decimal(0), verbose_name="现金支付收入") free_times_amount = IntegerField(default=0, verbose_name="次卡支付数量") parent_id = IntegerField(default=0) state = IntegerField(default=ActivityState.opening, index=True) class Meta: db_table = 'activity' order_by = ('-id', ) @property def creator_id(self): return self._data['creator'] @property def team_id(self): return self._data['team_id'] @property def sport_id(self): return self._data['sport'] def is_ended(self): return self.end_time and datetime.now() > self.end_time def is_started(self): return self.start_time and datetime.now() > self.start_time def is_finished(self): """活动已结算 """ return self.state == self.ActivityState.finished def can_cancel(self): if self.state in (self.ActivityState.finished, self.ActivityState.cancelled) \ or self.is_ended(): return False return True def can_change(self): return self.can_cancel() def can_apply(self): if self.state != self.ActivityState.opening: return "活动已经取消或结束" if (self.join_end, self.start_time)[True] < datetime.now(): return "活动已报名截止" if self.members_count >= self.max_members: return "活动人数已满" return True @property def state_name(self): if self.state in (self.ActivityState.closed, self.ActivityState.cancelled, self.ActivityState.finished): return self.STATES.get(self.ActivityState(self.state), "未知") if datetime.now() > self.end_time: return "待结算" if self.members_count >= self.max_members: return "已满员" if (self.join_end, self.start_time)[True] < datetime.now(): return "已截止" return "报名中" def add_member(self, user_id, users_count, price, free_times, total_fee, order_id=0, order_no="", payment_method="", payment_state=0, state=0, gps_enabled=True, join_type="", inviter=None, **extra_fields): """ 添加成员 Args: user_id: 用户ID users_count: 报名人数 price: 报名价格(记录报名时的价格) free_times: 使用次卡点数 total_fee: 总计费用=price(users_count-free_times) order_id: 订单ID order_no: 订单NO payment_state: 支付状态 payment_method: 支付方法 state: 状态(ActivityMemberState) gps_enabled: gps是否开始 join_type: 报名类型,标记报名用户来源 inviter: 邀请码 """ activity_member = Activity.get_member(self.id, user_id) if activity_member is not None: raise Exception("不能重复报名") ActivityMember.create( team_id=self.team_id, user=user_id, activity=self, created=datetime.now(), gps_enabled=gps_enabled, state=state, join_type=join_type, users_count=users_count, price=price, free_times=free_times, total_fee=total_fee, order_id=order_id, order_no=order_no, payment_method=payment_method, payment_state=payment_state, inviter=inviter, **extra_fields, ) Activity.update(members_count=Activity.members_count + users_count).where(Activity.id == self.id).execute() def leave(self, user): """ 用户退出活动 用户退出活动后如果已支付需要退款,将订单修改为申请退款状态并删除活动成员信息 Args: user: 用户对象 """ with (self._meta.database.transaction()): member = Activity.get_member(self.id, user.id) # 如果已支付需要退款 if member.payment_state == TeamOrder.OrderState.TRADE_BUYER_PAID: member.refund() ActivityMember.delete().where((ActivityMember.activity == self) & ( ActivityMember.user == user.id)).execute() Activity.update( members_count=Activity.get_members_count(self.id)).where( Activity.id == self.id).execute() def get_members(self, state=None): """ 获取活动成员列表 Args: state: 指定状态 Returns: Query """ q = User.select(User, ActivityMember).join( ActivityMember, on=ActivityMember.user).where(ActivityMember.activity == self) if state is not None: q = q.where(ActivityMember.state == state) q = q.order_by(ActivityMember.nickname.asc(), User.name.asc()) return q @classmethod def update_members_count(cls, activity_id): """ 更新活动成员数量统计 """ Activity.update( members_count=cls.get_members_count(activity_id)).where( Activity.id == activity_id).execute() @classmethod def get_members_count(cls, activity_id): """ 统计活动成员数量 """ return ActivityMember.select(fn.SUM(ActivityMember.users_count)).where( ActivityMember.activity == activity_id, ActivityMember.state == ActivityMember.ActivityMemberState.confirmed).scalar() or 0 @classmethod def is_member(cls, activity_id, user_id): """ 判断用户是否已报名指定活动场次 """ return ActivityMember.select().where( ActivityMember.user == user_id, ActivityMember.activity == activity_id).exists() @classmethod def get_member(cls, activity_id, user_id): try: return ActivityMember.select().where( (ActivityMember.activity == activity_id) & (ActivityMember.user == user_id)).get() except ActivityMember.DoesNotExist: return None def remove(self): """删除活动 不允许删除已有用户报名的活动 TODO:删除头像和照片 """ with (self.db.transaction()): ActivityMember.delete().where( ActivityMember.activity == self).execute() self.redis.delete("yy:activity:followers:%s" % self.id) self.redis.delete("yy:activity:members:%s" % self.id) self.redis.delete("yy:activity:comments:%s" % self.id) self.redis.hdel("yy:activity:views_count", self.id) self.delete_instance() @cached_property def info(self): _info = self.to_dict(exclude=[ Activity.creator, Activity.team, Activity.leader, Activity.sport ]) _info['creator_id'] = self._data['creator'] _info['team_id'] = self._data['team'] _info['leader_id'] = self._data['leader'] _info['sport'] = Sport.get( id=self.sport_id).info if self.sport_id else {} return _info def get_info(self): return self.info