несолько детей

remotes/origin/feature/childs-11-06-19
gzbender 7 years ago
parent a98587c9ca
commit bb8871141f
  1. 20
      apps/user/forms.py
  2. 26
      apps/user/migrations/0032_child.py
  3. 21
      apps/user/migrations/0033_add_childs.py
  4. 31
      apps/user/models.py
  5. 2
      apps/user/templates/user/bonus-history.html
  6. 99
      apps/user/templates/user/profile-settings.html
  7. 2
      apps/user/templates/user/profile.html
  8. 74
      apps/user/views.py
  9. 3
      project/tests/test_features.py
  10. 119
      web/src/components/Childs.vue
  11. 4
      web/src/js/pages/profile.js
  12. 4
      web/src/sass/_common.sass

@ -2,7 +2,8 @@ from django import forms
from django.contrib.auth import get_user_model
from phonenumber_field.formfields import PhoneNumberField
from .fields import CreditCardField
from apps.user.fields import CreditCardField
from apps.user.models import Child
User = get_user_model()
@ -31,11 +32,6 @@ class UserEditForm(forms.ModelForm):
site = forms.URLField(required=False)
photo = forms.ImageField(required=False)
child_gender = forms.CharField(required=False)
child_birthday = forms.DateField(input_formats=['%d/%m/%Y'], required=False)
child_first_name = forms.CharField(required=False)
child_last_name = forms.CharField(required=False)
class Meta:
model = User
fields = (
@ -61,10 +57,6 @@ class UserEditForm(forms.ModelForm):
'vkontakte',
'site',
'photo',
'child_gender',
'child_birthday',
'child_first_name',
'child_last_name',
)
@ -79,3 +71,11 @@ class AuthorRequesForm(forms.Form):
email = forms.CharField()
about = forms.CharField()
facebook = forms.URLField(required=False)
class ChildForm(forms.ModelForm):
birthday = forms.DateField(input_formats=['%d/%m/%Y'], required=False)
class Meta:
model = Child
fields = '__all__'

@ -0,0 +1,26 @@
# Generated by Django 2.0.7 on 2019-06-06 21:05
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('user', '0031_user_review_url'),
]
operations = [
migrations.CreateModel(
name='Child',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=30, verbose_name='Имя ребенка')),
('last_name', models.CharField(blank=True, default='', max_length=150, verbose_name='Фамилия ребенка')),
('gender', models.CharField(choices=[('n', 'не указан'), ('m', 'Мужчина'), ('f', 'Женщина')], default='n', max_length=1, verbose_name='Пол ребенка')),
('birthday', models.DateField(blank=True, null=True, verbose_name='День рождения ребенка')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='childs', to=settings.AUTH_USER_MODEL)),
],
),
]

@ -0,0 +1,21 @@
# Generated by Django 2.0.7 on 2019-06-07 20:48
from django.db import migrations
def add_childs(apps, schema_editor):
User = apps.get_model('user', 'User')
Child = apps.get_model('user', 'Child')
for user in User.objects.exclude(child_first_name=''):
Child.objects.get_or_create(user=user, first_name=user.child_first_name, last_name=user.child_last_name,
gender=user.child_gender, birthday=user.child_birthday)
class Migration(migrations.Migration):
dependencies = [
('user', '0032_child'),
]
operations = [
migrations.RunPython(add_childs),
]

@ -88,12 +88,6 @@ class User(AbstractUser):
'content.Gallery', on_delete=models.CASCADE,
verbose_name='Галерея', null=True, blank=True,
)
child_first_name = models.CharField('Имя ребенка', max_length=30, blank=True)
child_last_name = models.CharField('Фамилия ребенка', max_length=150, blank=True)
child_gender = models.CharField(
'Пол ребенка', max_length=1, default='n', choices=GENDER_CHOICES)
child_birthday = models.DateField('День рождения ребенка', null=True, blank=True)
review_url = models.URLField('Ссылка на видеоотзыв', blank=True, default='')
objects = UserManager()
@ -104,6 +98,14 @@ class User(AbstractUser):
class Meta(AbstractUser.Meta):
ordering = ('-date_joined',)
@cached_property
def has_child(self):
return self.childs.all().count() > 0
def child_filled(self):
child = self.childs.all().first()
return child and child.first_name and child.last_name and child.birthday
@property
def url(self):
return reverse('user', args=[self.slug or self.id])
@ -338,3 +340,20 @@ class EmailLog(models.Model):
email = models.EmailField(_('email address'))
created_at = models.DateTimeField(auto_now_add=True)
source = models.PositiveSmallIntegerField(choices=SOURCE_CHOICES)
class Child(models.Model):
NOT_DEFINED = 'n'
MALE = 'm'
FEMALE = 'f'
GENDER_CHOICES = (
(NOT_DEFINED, 'не указан'),
(MALE, 'Мужчина'),
(FEMALE, 'Женщина'),
)
user = models.ForeignKey(User, related_name='childs', on_delete=models.CASCADE)
first_name = models.CharField('Имя ребенка', max_length=30)
last_name = models.CharField('Фамилия ребенка', max_length=150, blank=True, default='')
gender = models.CharField(
'Пол ребенка', max_length=1, default='n', choices=GENDER_CHOICES)
birthday = models.DateField('День рождения ребенка', null=True, blank=True)

@ -83,7 +83,7 @@
</div>
<div>+{{ amount_child_birthday }} LIL</div>
<div><a class="btn btn_stroke" href="{% url 'user-profile' %}">ЗАПОЛНИТЬ КАРТОЧКУ РЕБЕНКА</a></div>
<div>{% if request.user.child_first_name and request.user.child_last_name and request.user.child_birthday %}
<div>{% if request.user.child_filled %}
<img src="{% static 'img/done2.png' %}" />{% endif %}</div>
</div>
</div>

@ -67,101 +67,41 @@
{% endfor %}
</div>
</div>
<div class="form__field field{% if form.email.errors %} error{% endif %}">
<div class="field__label">Почта</div>
<div class="field__wrap">
<input name='email' class="field__input" type="email" placeholder="Почта" value="{{ user.email }}">
</div>
{% for error in form.email.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
<div class="form__field field{% if form.slug.errors %} error{% endif %}">
<div class="field__label">Ссылка</div>
<div class="field__wrap field__wrap__appended">
<div class="field__text">{% setting 'MAIN_HOST' %}/user/</div>
<input name='slug' id="user-slug" class="field__input field__input__appended" type="text"
value="{{ user.slug|default:'' }}" data-current-slug="{{ user.slug|default:'' }}">
</div>
{% for error in form.slug.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
</div>
<!-- child form -->
<div class="user-child-form form__group">
<div class="form__title">Карточка ребёнка</div>
<div class="user-child-form__description">
Вся информация конфиденциальна и не передается третьим лицам. Необходима только для персонализации наград,
поздравлений с Днем Рождения и других персонализированных акций.
</div>
<div class="form__fieldset">
<div class="form__field field{% if form.child_first_name.errors %} error{% endif %}">
<div class="field__label">ИМЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_first_name' id="child-first-name" class="field__input" type="text" placeholder="Имя"
value="{{ user.child_first_name }}">
</div>
{% for error in form.child_first_name.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
<div class="form__field field{% if form.child_last_name.errors %} error{% endif %}">
<div class="field__label">ФАМИЛИЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_last_name' id="child-last-name" class="field__input" type="text" placeholder="Фамилия"
value="{{ user.child_last_name }}">
</div>
{% for error in form.child_last_name.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
</div>
<div class="form__fieldset">
<div class="form__field field{% if form.child_birthday.errors %} error{% endif %}">
<div class="field__label">ДАТА РОЖДЕНИЯ</div>
<div class="form__field field{% if form.email.errors %} error{% endif %}">
<div class="field__label">Почта</div>
<div class="field__wrap">
<vue-datepicker input-class="field__input" name="child_birthday" language="ru" format="dd/MM/yyyy"
value="{% if user.child_birthday %}{{ user.child_birthday | date:'Y-m-d' }}{% endif %}" placeholder="dd/mm/yyyy"/>
<input name='email' class="field__input" type="email" placeholder="Почта" value="{{ user.email }}">
</div>
{% for error in form.child_birthday.errors %}
{% for error in form.email.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
<div class="form__field field{% if form.child_gender.errors %} error{% endif %}">
<div class="field__label">ПОЛ</div>
<div class="form__field field{% if form.phone.errors %} error{% endif %}">
<div class="field__label">НОМЕР ТЕЛЕФОНА</div>
<div class="field__wrap">
<div class="field__select select js-select{% if user.child_gender and user.child_gender != 'n' %} selected{% endif %}">
<div class="select__head js-select-head">
{% if user.child_gender == 'f' %}Ж{% elif user.child_gender == 'm' %}M{% else %}М / Ж{% endif %}
</div>
<div class="select__drop js-select-drop" data-gender-select data-input="#child-gender">
<div class="select__option js-select-option" data-gender-option data-gender="m">
<div class="select__title">М</div>
</div>
<div class="select__option js-select-option" data-gender-option data-gender="f">
<div class="select__title">Ж</div>
</div>
</div>
<input id="child-gender" name='child_gender' class="select__input" type="hidden">
</div>
<input name='phone' class="field__input" type="phone" placeholder="+7 (999) 999-99-99" value="{{ user.phone|default:'' }}">
</div>
{% for error in form.child_gender.errors %}
{% for error in form.phone.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
</div>
<div class="form__field field{% if form.phone.errors %} error{% endif %}">
<div class="field__label">НОМЕР ТЕЛЕФОНА РОДИТЕЛЯ</div>
<div class="field__wrap">
<input name='phone' class="field__input" type="phone" placeholder="+7 (999) 999-99-99" value="{{ user.phone|default:'' }}">
<div class="form__field field{% if form.slug.errors %} error{% endif %}">
<div class="field__label">Ссылка</div>
<div class="field__wrap field__wrap__appended">
<div class="field__text">{% setting 'MAIN_HOST' %}/user/</div>
<input name='slug' id="user-slug" class="field__input field__input__appended" type="text"
value="{{ user.slug|default:'' }}" data-current-slug="{{ user.slug|default:'' }}">
</div>
{% for error in form.phone.errors %}
{% for error in form.slug.errors %}
<div class="field__error">{{ error }}</div>
{% endfor %}
</div>
</div>
<!-- end child form -->
<childs :childs="$root.store.data.childs"></childs>
<div class="form__group">
<div class="form__fieldset">
<div class="form__field field{% if form.city.errors %} error{% endif %}">
@ -393,5 +333,8 @@
{% endblock foot %}
{% block pre_app_js %}
<script>
window.LIL_STORE.data.childs = {{ childs|safe }};
</script>
<script type="text/javascript" src="{% static 'profile.js' %}"></script>
{% endblock pre_app_js %}

@ -1,6 +1,6 @@
{% extends "templates/lilcity/index.html" %} {% load static %} {% load thumbnail %} {% block content %}
{% if not user.child_first_name or not user.child_birthday %}
{% if not user.has_child %}
<div class="section">
<div class="section__center center">
<div class="fill-profile">

@ -26,9 +26,9 @@ from apps.notification.utils import send_email
from apps.school.models import SchoolSchedule
from apps.payment.models import AuthorBalance, CoursePayment, SchoolPayment, Payment, UserGiftCertificate, UserBonus, \
DrawingCampPayment
from apps.user.models import AuthorRequest, EmailSubscription, SubscriptionCategory
from apps.user.models import AuthorRequest, EmailSubscription, SubscriptionCategory, Child
from .forms import AuthorRequesForm, UserEditForm, WithdrawalForm
from .forms import AuthorRequesForm, UserEditForm, WithdrawalForm, ChildForm
User = get_user_model()
@ -217,6 +217,26 @@ class ProfileEditView(UpdateView):
context['is_teacher'] = self.object.role == User.TEACHER_ROLE
return context
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
if self.object.childs.all().count():
context['childs'] = [{
'id': c.id,
'first_name': c.first_name,
'last_name': c.last_name,
'gender': c.gender or 'n',
'birthday': c.birthday.strftime('%Y-%m-%d') if c.birthday else '',
} for c in self.object.childs.all()]
else:
context['childs'] = [{
'id': '',
'first_name': '',
'last_name': '',
'gender': 'n',
'birthday': '',
}]
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
# it's magic *-*-*-*-*
if 'photo' in request.FILES:
@ -256,15 +276,47 @@ class ProfileEditView(UpdateView):
login(request, request.user)
else:
messages.error(request, 'Неверный пароль.')
messages.info(request, 'Данные сохранены.')
response = super().post(request, *args, **kwargs)
user = User.objects.get(pk=request.user.id)
# начисляемм бонусы, если заполнил профиль
if user.phone and user.first_name and user.last_name and not user.bonuses.filter(
is_service=True, action_name=UserBonus.ACTION_FILL_PROFILE).count():
UserBonus.objects.create(user=user, amount=UserBonus.AMOUNT_FILL_PROFILE, is_service=True,
action_name=UserBonus.ACTION_FILL_PROFILE)
return response
child_ids = request.POST.getlist('child_id')
child_first_names = request.POST.getlist('child_first_name')
child_last_names = request.POST.getlist('child_last_name')
child_genders = request.POST.getlist('child_gender')
child_birthdays = request.POST.getlist('child_birthday')
ziped = zip(child_ids, child_first_names, child_last_names, child_genders, child_birthdays)
childs = [dict(zip(['id', 'first_name', 'last_name', 'gender', 'birthday'], z)) for z in ziped]
childs_saved = []
child_errors = False
for child in childs:
child_instance = None
if child.get('id'):
child_instance = Child.objects.get(pk=child.get('id'))
child['user'] = request.user.id
child['gender'] = child['gender'] or 'n'
child_form = ChildForm(data=child, instance=child_instance)
if child_form.is_valid():
childs_saved.append(child_form.save().id)
elif not(len(childs) == 1 and not child.get('id') and not child.get('first_name')):
child_errors = True
child['errors'] = {f: e[0] for f, e in child_form.errors.items()}
if not child_errors:
request.user.childs.exclude(id__in=childs_saved).delete()
form = self.get_form()
if form.is_valid() and not child_errors:
messages.info(request, 'Данные сохранены.')
response = self.form_valid(form)
# начисляем бонусы, если заполнил профиль
if self.object.phone and self.object.first_name and self.object.last_name and not self.object.bonuses.filter(
is_service=True, action_name=UserBonus.ACTION_FILL_PROFILE).count():
UserBonus.objects.create(user=self.object, amount=UserBonus.AMOUNT_FILL_PROFILE, is_service=True,
action_name=UserBonus.ACTION_FILL_PROFILE)
return response
else:
context = self.get_context_data(**kwargs)
context['childs'] = childs
return self.render_to_response(context)
def get_success_url(self):
return reverse('user-edit-profile')

@ -59,8 +59,7 @@ class AutoAddingUserBonusTestCase(TestCase):
user = UserFactory.create(role=User.USER_ROLE, first_name='', last_name='', phone='')
self.assertEqual(user.bonus, 0, 'При создании есть бонусы')
self.client.force_login(user)
data = {k: user.__dict__[k] for k in ['email', 'slug', 'child_first_name',
'child_last_name', 'child_birthday', 'child_gender', 'city',
data = {k: user.__dict__[k] for k in ['email', 'slug', 'city',
'country', 'birthday', 'gender', 'about', 'instagram', 'facebook',
'twitter', 'pinterest', 'youtube', 'vkontakte', 'site']}

@ -0,0 +1,119 @@
<template>
<div class="user-child-form form__group">
<div class="form__title">Карточка ребёнка</div>
<div class="user-child-form__description">
Вся информация конфиденциальна и не передается третьим лицам. Необходима только для персонализации наград,
поздравлений с Днем Рождения и других персонализированных акций.
</div>
<div class="tabs">
<div class="user-child-form__tabs tabs__nav">
<a class="user-child-form__tab tabs__btn" v-for="(child, index) in childs" :key="index" href="#"
v-bind:class="{active: selectedIndex == index}" @click.prevent="select(index)">
{{ child.first_name ? (child.first_name + '&nbsp;' + child.last_name) : ('Ребенок ' + index) }}</a>
<a href="#" class="tabs__btn" @click.prevent="add()">Добавить ребенка</a>
</div>
</div>
<div class="tabs__container">
<div v-for="(child, index) in childs" :key="index" class="tabs__item js-tabs-item"
v-bind:style="{display: selectedIndex == index ? 'block' : ''}">
<div class="form__fieldset">
<input type="hidden" name="child_id" :value="child.id" />
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.gender}">
<div class="field__label">ИМЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_first_name' class="field__input" type="text" placeholder="Имя"
v-model="child.first_name">
</div>
<div v-if="child.errors && child.errors.first_name" class="field__error">{{ child.errors.first_name }}</div>
</div>
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.last_name}">
<div class="field__label">ФАМИЛИЯ РЕБЕНКА</div>
<div class="field__wrap">
<input name='child_last_name' id="child-last-name" class="field__input" type="text" placeholder="Фамилия"
v-model="child.last_name">
</div>
<div v-if="child.errors && child.errors.last_name" class="field__error">{{ child.errors.last_name }}</div>
</div>
</div>
<div class="form__fieldset">
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.birthday}">
<div class="field__label">ДАТА РОЖДЕНИЯ</div>
<div class="field__wrap">
<vue-datepicker input-class="field__input" name="child_birthday" language="ru" format="dd/MM/yyyy"
v-model="child.birthday" placeholder="dd/mm/yyyy"/>
</div>
<div v-if="child.errors && child.errors.birthday" class="field__error">{{ child.errors.birthday }}</div>
</div>
<div class="form__field field" v-bind:class="{error: child.errors && child.errors.gender}">
<div class="field__label">ПОЛ</div>
<div class="field__wrap">
<lil-select :value.sync="child.gender" :options="genders" value-key="0" title-key="1"/>
<input name='child_gender' type="hidden" v-model="child.gender">
</div>
<div v-if="child.errors && child.errors.gender" class="field__error">{{ child.errors.gender }}</div>
</div>
</div>
<a href="#" @click.prevent="remove(index)">Удалить</a>
</div>
</div>
</div>
</template>
<script>
import DatePicker from 'vuejs-datepicker';
import LilSelect from './inputs/LilSelect';
export default {
name: "childs",
props: ['childs'],
data() {
return {
selectedIndex: 0,
selectedChild: null,
genders: [
['n', 'М / Ж'],
['f', 'Ж'],
['m', 'М'],
]
}
},
methods: {
add(){
this.childs.push({
first_name: '',
last_name: '',
birthday: '',
errors: {},
});
this.select(this.childs.length - 1);
},
remove(index){
if(! confirm('Вы действительно хотите удалить данные о ребенке?')){
return;
}
this.childs.splice(index, 1);
if(! this.childs.length){
this.add();
}
else if(this.selectedIndex == index){
this.select(0);
}
},
select(index){
this.selectedIndex = index;
this.selectedChild = this.childs[index];
}
},
mounted(){
for(let i in this.childs){
if(this.childs[i].errors){
this.select(i);
}
}
},
components: {
'vue-datepicker': DatePicker,
LilSelect,
}
}
</script>

@ -2,6 +2,10 @@ import $ from 'jquery';
import slugify from 'slugify';
import ClipboardJS from 'clipboard';
import {showNotification} from '../modules/notification';
import Childs from '../../components/Childs.vue';
window.LIL_STORE.components['childs'] = Childs;
$(document).ready(function () {
if(window.LIL_STORE.urlIs('userBonuses')){

@ -4711,6 +4711,10 @@ a
color: #333333
font-size: 12px
&__tabs
justify-content: left
overflow-x: auto
overflow-y: hidden
.bonuses-table
margin-left: -120px

Loading…
Cancel
Save