Commit ca734fd7 authored by Guilhem Saurel's avatar Guilhem Saurel
Browse files

big refactor wip…

parent d6ac68a2
...@@ -15,7 +15,14 @@ export REDMINE_TOKEN=xxx ...@@ -15,7 +15,14 @@ export REDMINE_TOKEN=xxx
export OPENROB_TOKEN=xxx export OPENROB_TOKEN=xxx
./manage.py migrate ./manage.py migrate
./manage.py populate ./manage.py populate
./manage.py fetch
./manage.py robotpkg
./manage.py cmake
./manage.py travis
./manage.py update
./manage.py runserver ./manage.py runserver
``` ```
or, as a shortcut, `./launch.sh`
You can go to http://localhost:8000 You can go to http://localhost:8000
#!/bin/bash
set -ex
source .env
./manage.py migrate
./manage.py populate
./manage.py fetch
./manage.py robotpkg
./manage.py cmake
./manage.py travis
./manage.py update
./manage.py runserver
...@@ -6,6 +6,7 @@ from rainboard.models import Project ...@@ -6,6 +6,7 @@ from rainboard.models import Project
logger = logging.getLogger('rainboard.management.fetch') logger = logging.getLogger('rainboard.management.fetch')
class Command(BaseCommand): class Command(BaseCommand):
help = 'Fetch all remotes' help = 'Fetch all remotes'
......
...@@ -4,11 +4,12 @@ from django.core.management.base import BaseCommand ...@@ -4,11 +4,12 @@ from django.core.management.base import BaseCommand
import requests import requests
from rainboard.models import Forge, License, Repo from rainboard.models import Forge, License, Repo, Project
LICENSES = 'https://raw.githubusercontent.com/spdx/license-list-data/master/json/licenses.json' LICENSES = 'https://raw.githubusercontent.com/spdx/license-list-data/master/json/licenses.json'
logger = logging.getLogger('rainboard.management.populate') logger = logging.getLogger('rainboard.management.populate')
class Command(BaseCommand): class Command(BaseCommand):
help = 'populates licenses, projets, namespaces and repos from forges' help = 'populates licenses, projets, namespaces and repos from forges'
...@@ -23,10 +24,10 @@ class Command(BaseCommand): ...@@ -23,10 +24,10 @@ class Command(BaseCommand):
# for data in requests.get(f'{github.api_url()}/licenses', headers=github.headers()).json(): # for data in requests.get(f'{github.api_url()}/licenses', headers=github.headers()).json():
# logger.info(f' updating license {data["name"]}') # logger.info(f' updating license {data["name"]}')
# License.objects.get_or_create(spdx_id=data['spdx_id'], # License.objects.get_or_create(spdx_id=data['spdx_id'],
# defaults={key: data[key] for key in ['name', 'url']}) # defaults={key: data[key] for key in ['name', 'url']})
logger.info(f'updating forges') logger.info(f'updating forges')
for forge in Forge.objects.all(): for forge in Forge.objects.order_by('source'):
logger.info(f' updating {forge}') logger.info(f' updating {forge}')
forge.get_projects() forge.get_projects()
...@@ -34,3 +35,6 @@ class Command(BaseCommand): ...@@ -34,3 +35,6 @@ class Command(BaseCommand):
for repo in Repo.objects.all(): for repo in Repo.objects.all():
logger.info(f' updating {repo}') logger.info(f' updating {repo}')
repo.api_update() repo.api_update()
logger.info(f'removing unwanted projects')
Project.objects.filter(main_namespace__group=False).delete()
import logging import logging
from django.core.management.base import BaseCommand
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand
from rainboard.models import Project, Robotpkg
import git import git
from rainboard.models import Project, Robotpkg
logger = logging.getLogger('rainboard.robotpkg') logger = logging.getLogger('rainboard.robotpkg')
......
...@@ -3,9 +3,7 @@ import os ...@@ -3,9 +3,7 @@ import os
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
import requests from rainboard.models import SOURCES, Forge, Namespace, Project, Repo
from rainboard.models import Repo, Namespace, Forge, SOURCES, Project
logger = logging.getLogger('rainboard.management.travis') logger = logging.getLogger('rainboard.management.travis')
......
import logging
from django.conf import settings
from django.core.management.base import BaseCommand
import git
from rainboard.models import Branch, Project, Repo, Robotpkg
logger = logging.getLogger('rainboard.management.update')
class Command(BaseCommand):
help = 'Update the DB'
def handle(self, *args, **options):
logger.info(f'\nUpdating all repos\n')
for repo in Repo.objects.all():
logger.info(f' {repo}')
repo.update()
logger.info(f'\nUpdating all branches\n')
for branch in Branch.objects.all():
logger.info(f' {branch}')
branch.update(pull=False)
logger.info(f'\nPulling Robotpkg\n')
git.Repo(str(settings.RAINBOARD_RPKG / '.git')).remotes.origin.pull()
git.Repo(str(settings.RAINBOARD_RPKG / 'wip' / '.git')).remotes.origin.pull()
logger.info(f'\nUpdating Robotpkg\n')
for robotpkg in Robotpkg.objects.all():
logger.info(f' {robotpkg}')
robotpkg.update(pull=False)
logger.info(f'\nUpdating all projects\n')
for project in Project.objects.all():
logger.info(f' {project}')
project.update()
# Generated by Django 2.0.1 on 2018-01-12 18:05 # Generated by Django 2.0.1 on 2018-02-06 18:47
import autoslug.fields import autoslug.fields
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import ndh.models import ndh.models
import rainboard.utils
class Migration(migrations.Migration): class Migration(migrations.Migration):
...@@ -36,12 +37,23 @@ class Migration(migrations.Migration): ...@@ -36,12 +37,23 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)), ('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)), ('name', models.CharField(max_length=200)),
('name', models.CharField(max_length=200, unique=True)), ('ahead', models.PositiveSmallIntegerField(blank=True, null=True)),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)), ('behind', models.PositiveSmallIntegerField(blank=True, null=True)),
('updated', models.DateTimeField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='CIBuild',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('passed', models.NullBooleanField()),
('build_id', models.PositiveIntegerField()),
('started', models.DateTimeField()),
('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Branch')),
], ],
options={ options={
'abstract': False, 'ordering': ('started',),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
...@@ -53,9 +65,6 @@ class Migration(migrations.Migration): ...@@ -53,9 +65,6 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=200, unique=True)), ('name', models.CharField(max_length=200, unique=True)),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)), ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)),
], ],
options={
'abstract': False,
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Forge', name='Forge',
...@@ -63,7 +72,7 @@ class Migration(migrations.Migration): ...@@ -63,7 +72,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, unique=True)), ('name', models.CharField(max_length=200, unique=True)),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)), ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)),
('source', models.PositiveSmallIntegerField(choices=[(1, 'github'), (2, 'gitlab'), (3, 'redmine'), (4, 'robotpkg')])), ('source', models.PositiveSmallIntegerField(choices=[(1, 'github'), (2, 'gitlab'), (3, 'redmine'), (4, 'robotpkg'), (5, 'travis')])),
('url', models.URLField()), ('url', models.URLField()),
('token', models.CharField(blank=True, max_length=50, null=True)), ('token', models.CharField(blank=True, max_length=50, null=True)),
('verify', models.BooleanField(default=True)), ('verify', models.BooleanField(default=True)),
...@@ -99,13 +108,16 @@ class Migration(migrations.Migration): ...@@ -99,13 +108,16 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)), ('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=200, unique=True)), ('name', models.CharField(max_length=200, unique=True)),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)), ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)),
('private', models.BooleanField(default=False)), ('private', models.BooleanField(default=False)),
('homepage', models.URLField(blank=True, null=True)), ('homepage', models.URLField(blank=True, null=True)),
('description', models.TextField()),
('version', models.CharField(blank=True, max_length=20, null=True)),
('updated', models.DateTimeField(blank=True, null=True)),
('articles', models.ManyToManyField(to='rainboard.Article')), ('articles', models.ManyToManyField(to='rainboard.Article')),
('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rainboard.License')), ('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rainboard.License')),
('main_forge', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rainboard.Forge')),
('main_namespace', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rainboard.Namespace')), ('main_namespace', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rainboard.Namespace')),
], ],
options={ options={
...@@ -120,7 +132,7 @@ class Migration(migrations.Migration): ...@@ -120,7 +132,7 @@ class Migration(migrations.Migration):
('created', models.DateTimeField(auto_now_add=True)), ('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)), ('updated', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=200)), ('name', models.CharField(max_length=200)),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name')), ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', slugify=rainboard.utils.slugify_with_dots)),
('homepage', models.URLField(blank=True, null=True)), ('homepage', models.URLField(blank=True, null=True)),
('url', models.URLField(blank=True, null=True)), ('url', models.URLField(blank=True, null=True)),
('default_branch', models.CharField(max_length=50)), ('default_branch', models.CharField(max_length=50)),
...@@ -129,6 +141,7 @@ class Migration(migrations.Migration): ...@@ -129,6 +141,7 @@ class Migration(migrations.Migration):
('repo_id', models.PositiveIntegerField()), ('repo_id', models.PositiveIntegerField()),
('forked_from', models.PositiveIntegerField(blank=True, null=True)), ('forked_from', models.PositiveIntegerField(blank=True, null=True)),
('clone_url', models.URLField(blank=True, null=True)), ('clone_url', models.URLField(blank=True, null=True)),
('travis_id', models.PositiveIntegerField(blank=True, null=True)),
('forge', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Forge')), ('forge', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Forge')),
('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rainboard.License')), ('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rainboard.License')),
('namespace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Namespace')), ('namespace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Namespace')),
...@@ -144,9 +157,19 @@ class Migration(migrations.Migration): ...@@ -144,9 +157,19 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, unique=True)), ('name', models.CharField(max_length=200, unique=True)),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)), ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)),
('homepage', models.URLField(blank=True, null=True)), ('category', models.CharField(max_length=50)),
('pkgbase', models.CharField(default='', max_length=50)),
('pkgversion', models.CharField(default='', max_length=20)),
('master_sites', models.CharField(default='', max_length=200)),
('master_repository', models.CharField(default='', max_length=200)),
('maintainer', models.CharField(default='', max_length=200)),
('comment', models.TextField()),
('homepage', models.URLField(default='')),
('private', models.BooleanField(default=False)),
('description', models.TextField()),
('updated', models.DateTimeField(blank=True, null=True)),
('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rainboard.License')), ('license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rainboard.License')),
('project', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Project')), ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Project')),
], ],
options={ options={
'abstract': False, 'abstract': False,
...@@ -158,7 +181,7 @@ class Migration(migrations.Migration): ...@@ -158,7 +181,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)), ('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)), ('updated', models.DateTimeField(auto_now=True)),
('target', models.PositiveSmallIntegerField(choices=[(1, '12.04'), (2, '14.04'), (3, '16.04'), (4, 'dubnium')])), ('target', models.PositiveSmallIntegerField(choices=[(1, '14.04'), (2, '16.04'), (3, '17.10'), (4, '18.04'), (5, 'dubnium')])),
('passed', models.BooleanField(default=False)), ('passed', models.BooleanField(default=False)),
('robotpkg', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Robotpkg')), ('robotpkg', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Robotpkg')),
], ],
...@@ -167,11 +190,12 @@ class Migration(migrations.Migration): ...@@ -167,11 +190,12 @@ class Migration(migrations.Migration):
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='RobotpkgDependency', name='SystemDependency',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, unique=True)), ('name', models.CharField(max_length=200, unique=True)),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)), ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)),
('target', models.PositiveSmallIntegerField(choices=[(1, '14.04'), (2, '16.04'), (3, '17.10'), (4, '18.04'), (5, 'dubnium')])),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Project')), ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Project')),
], ],
options={ options={
...@@ -179,16 +203,15 @@ class Migration(migrations.Migration): ...@@ -179,16 +203,15 @@ class Migration(migrations.Migration):
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='SystemDependency', name='Tag',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, unique=True)), ('name', models.CharField(max_length=200)),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', unique=True)), ('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='name', slugify=rainboard.utils.slugify_with_dots)),
('target', models.PositiveSmallIntegerField(choices=[(1, '12.04'), (2, '14.04'), (3, '16.04'), (4, 'dubnium')])),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Project')), ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Project')),
], ],
options={ options={
'abstract': False, 'ordering': ('name',),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
...@@ -197,7 +220,7 @@ class Migration(migrations.Migration): ...@@ -197,7 +220,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)), ('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)), ('updated', models.DateTimeField(auto_now=True)),
('target', models.PositiveSmallIntegerField(choices=[(1, '12.04'), (2, '14.04'), (3, '16.04'), (4, 'dubnium')])), ('target', models.PositiveSmallIntegerField(choices=[(1, '14.04'), (2, '16.04'), (3, '17.10'), (4, '18.04'), (5, 'dubnium')])),
('passed', models.BooleanField(default=False)), ('passed', models.BooleanField(default=False)),
('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Branch')), ('branch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Branch')),
('commit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Commit')), ('commit', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Commit')),
...@@ -212,14 +235,31 @@ class Migration(migrations.Migration): ...@@ -212,14 +235,31 @@ class Migration(migrations.Migration):
name='project', name='project',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Project'), field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Project'),
), ),
migrations.AddField(
model_name='cibuild',
name='repo',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Repo'),
),
migrations.AddField( migrations.AddField(
model_name='branch', model_name='branch',
name='commit', name='project',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Commit'), field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Project'),
), ),
migrations.AddField( migrations.AddField(
model_name='branch', model_name='branch',
name='repo', name='repo',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rainboard.Repo'), field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='rainboard.Repo'),
),
migrations.AlterUniqueTogether(
name='tag',
unique_together={('name', 'project')},
),
migrations.AlterUniqueTogether(
name='commit',
unique_together={('project', 'name')},
),
migrations.AlterUniqueTogether(
name='branch',
unique_together={('project', 'name')},
), ),
] ]
...@@ -20,6 +20,13 @@ def forges(apps, schema_editor): ...@@ -20,6 +20,13 @@ def forges(apps, schema_editor):
Forge.objects.create(name='Openrobots', source=SOURCES.redmine, url='https://git.openrobots.org', Forge.objects.create(name='Openrobots', source=SOURCES.redmine, url='https://git.openrobots.org',
token=os.getenv('OPENROB_TOKEN')) token=os.getenv('OPENROB_TOKEN'))
def groups(apps, schema_editor):
Namespace = apps.get_model('rainboard', 'Namespace')
Namespace.objects.create(name='Humanoid Path Planner', group=True)
Namespace.objects.create(name='Stack Of Tasks', group=True)
Namespace.objects.create(name='Gepetto', group=True)
Namespace.objects.create(name='Pyrène Dev', group=True)
class Migration(migrations.Migration): class Migration(migrations.Migration):
...@@ -29,4 +36,5 @@ class Migration(migrations.Migration): ...@@ -29,4 +36,5 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.RunPython(forges), migrations.RunPython(forges),
migrations.RunPython(groups),
] ]
# Generated by Django 2.0.1 on 2018-01-29 16:43
import autoslug.fields
from django.db import migrations
import rainboard.utils
class Migration(migrations.Migration):
dependencies = [
('rainboard', '0002_forges'),
]
operations = [
migrations.AlterField(
model_name='repo',
name='slug',
field=autoslug.fields.AutoSlugField(editable=False, populate_from='name', slugify=rainboard.utils.slugify_with_dots),
),
]
# Generated by Django 2.0.1 on 2018-01-29 17:12
from django.db import migrations, models
import django.db.models.deletion
def forges(apps, schema_editor):
Forge = apps.get_model('rainboard', 'Forge')
forges = [Forge.objects.get(name=forge) for forge in ['Github', 'Gitlab', 'Redmine', 'Openrobots']]
Project = apps.get_model('rainboard', 'Project')
for project in Project.objects.all():
for forge in forges:
if project.repo_set.filter(forge=forge).exists():
project.main_forge = forge
project.save()
break
class Migration(migrations.Migration):
dependencies = [
('rainboard', '0003_slugify_with_dots'),
]
operations = [
migrations.AddField(
model_name='project',
name='main_forge',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rainboard.Forge'),
),
migrations.RunPython(forges),
]
# Generated by Django 2.0.1 on 2018-01-30 14:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('rainboard', '0004_project_main_forge'),
]
operations = [
migrations.RemoveField(
model_name='branch',
name='commit',
),
migrations.RemoveField(
model_name='branch',
name='repo',
),
migrations.AddField(
model_name='branch',
name='ahead',
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='branch',
name='behind',
field=models.PositiveSmallIntegerField(blank=True, null=True