Пример #1
0
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('手机号码格式不正确')
Пример #2
0
    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()
                    )
Пример #3
0
    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]
Пример #4
0
    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())
Пример #7
0
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", "不允许任何人加入")
        ])
Пример #8
0
    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())
Пример #9
0
    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()
                    )
Пример #10
0
    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)
Пример #11
0
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="长度不符合要求")
        ])
Пример #12
0
    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=[])
Пример #13
0
    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)
Пример #14
0
    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())
Пример #15
0
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('手机号码格式不正确')
Пример #16
0
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()])
Пример #17
0
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)
Пример #18
0
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', '照片')))
Пример #19
0
    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)
Пример #20
0
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)
                                  ])
Пример #21
0
 def get(self):
     self.write(ChinaCity.get_cities())
Пример #22
0
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