Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python/Django implementation #1

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@
node_modules
.log
*.pyc

# Python Distribution / packaging
.Python
env/
.env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include LICENSE
include README.md
include python/README.md
include data.json
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ If you are a node user, running `npm install && npm start` will rebuild the SQLi
Make this repository a dependency of your project and automate the process of copying `fantasy.db` into your testing harness.

Because this repository is meant to be used by multiple programming languages, there are no affordances for auto-migrating your database (PRs welcome!). Use [schema.sql](https://github.com/endpoints/fantasy-database/blob/master/schema.sql) as a reference for building migrations, and [data.json](https://github.com/endpoints/fantasy-database/blob/master/data.json) for seeding if you'd like to test in something other than SQLite.

#### Usage notes
- [Python](python)
38 changes: 38 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

# fantasy-database

> A database with a few fantasy books in it for testing query builders, orms, rest frameworks, etc.


## Django

This test app for Django provides models, migrations, and fixtures that implement the fantasy-database. It's
recommended that app installation be limited to testing only.

Example usage:

In your `settings.py` module:

```python

# Testing
TESTING = len(sys.argv) > 1 and sys.argv[1] in ('test', 'testserver')

if TESTING:
INSTALLED_APPS += (
'django_fantasy',
)

...
```

In a test module:
```python

from django.test import TestCase

class FantasyTests(TestCase):
fixtures = ['fantasy-database.json']
...

```
2 changes: 2 additions & 0 deletions python/django_fantasy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

default_app_config = 'django_fantasy.apps.FantasyConfig'
8 changes: 8 additions & 0 deletions python/django_fantasy/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

from django.apps import AppConfig


class FantasyConfig(AppConfig):
name = 'django_fantasy'
label = 'fantasy'
verbose_name = "Fantasy Database"
1 change: 1 addition & 0 deletions python/django_fantasy/fixtures/fantasy-database.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// This will be replaced on setup
102 changes: 102 additions & 0 deletions python/django_fantasy/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

dependencies = [
('contenttypes', '0002_remove_content_type_name'),
]

operations = [
migrations.CreateModel(
name='Author',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=80)),
('date_of_birth', models.DateField()),
('date_of_death', models.DateField(null=True)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Book',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=80)),
('date_published', models.DateField()),
('author', models.ForeignKey(to='fantasy.Author')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Chapter',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=80)),
('ordering', models.PositiveIntegerField()),
('book', models.ForeignKey(to='fantasy.Book')),
],
),
migrations.CreateModel(
name='Photo',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=80)),
('uri', models.URLField()),
('imageable_id', models.PositiveIntegerField()),
('imageable_type', models.ForeignKey(to='contenttypes.ContentType')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Series',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=80)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Store',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('name', models.CharField(max_length=80)),
('books', models.ManyToManyField(to='fantasy.Book')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='book',
name='series',
field=models.ForeignKey(to='fantasy.Series', null=True),
),
migrations.AlterUniqueTogether(
name='chapter',
unique_together=set([('book', 'ordering')]),
),
]
Empty file.
59 changes: 59 additions & 0 deletions python/django_fantasy/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType


class TimestampedModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

class Meta:
abstract = True


class Author(TimestampedModel):
name = models.CharField(max_length=80)
date_of_birth = models.DateField()
date_of_death = models.DateField(null=True)


class Series(TimestampedModel):
title = models.CharField(max_length=80)
photo = GenericRelation(
'fantasy.Photo',
content_type_field='imageable_type',
object_id_field='imageable_id',
)


class Book(TimestampedModel):
series = models.ForeignKey('fantasy.Series', null=True)
author = models.ForeignKey('fantasy.Author')
title = models.CharField(max_length=80)
date_published = models.DateField()


class Chapter(TimestampedModel):
title = models.CharField(max_length=80)
book = models.ForeignKey('fantasy.Book')
ordering = models.PositiveIntegerField()

class Meta:
unique_together = (
('book', 'ordering'),
)


class Store(TimestampedModel):
name = models.CharField(max_length=80)
books = models.ManyToManyField('fantasy.Book')


class Photo(TimestampedModel):
title = models.CharField(max_length=80)
uri = models.URLField()

imageable_type = models.ForeignKey(ContentType)
imageable_id = models.PositiveIntegerField()
imageable_object = GenericForeignKey('imageable_type', 'imageable_id')
103 changes: 103 additions & 0 deletions python/setup_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@

import os
import sys
import json
from setuptools.command.build_py import build_py
from setuptools.command.test import test

BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))


def build_fixture(input_path, output_path):
from utils import translate_django_fixture

contents = None
with open(input_path) as infile:
contents = json.loads(infile.read())

contents = translate_django_fixture(contents)

with open(output_path, 'w') as outfile:
outfile.write(json.dumps(contents))


class BuildPy(build_py):

# adapted from:
# http://www.digip.org/blog/2011/01/generating-data-files-in-setup.py.html
def run(self):
# honor the --dry-run flag
if not self.dry_run:
target_dir = os.path.join(self.build_lib, 'django_fantasy/fixtures')

# mkpath is a distutils helper to create directories
self.mkpath(target_dir)

input_path = os.path.join(BASE_DIR, 'data.json')
output_path = os.path.join(target_dir, 'fantasy-database.json')

build_fixture(input_path, output_path)

# distutils uses old-style classes, so no super()
build_py.run(self)


class Test(test):
user_options = [
('test-labels=', 'l', "Test labels to pass to runner.py test"),
('djtest-args=', 'a', "Arguments to pass to runner.py test"),
]

def initialize_options(self):
test.initialize_options(self)
self.test_labels = 'tests'
self.djtest_args = ''

def finalize_options(self):
test.finalize_options(self)
self.test_args = []
self.test_suite = True

# This is almost a direct copy of the original method. The difference is
# that this method only performs a non-'inplace' build.
def with_project_on_sys_path(self, func):
from pkg_resources import (
normalize_path, working_set, add_activation_listener, require
)

# Ensure metadata is up-to-date
self.reinitialize_command('build_py', inplace=0)
self.run_command('build_py')
bpy_cmd = self.get_finalized_command("build_py")
build_path = normalize_path(bpy_cmd.build_lib)

# Build extensions
self.reinitialize_command('egg_info', egg_base=build_path)
self.run_command('egg_info')

self.reinitialize_command('build_ext', inplace=0)
self.run_command('build_ext')

ei_cmd = self.get_finalized_command("egg_info")

old_path = sys.path[:]
old_modules = sys.modules.copy()

try:
sys.path.insert(0, normalize_path(ei_cmd.egg_base))
working_set.__init__()
add_activation_listener(lambda dist: dist.activate())
require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version))
func()
finally:
sys.path[:] = old_path
sys.modules.clear()
sys.modules.update(old_modules)
working_set.__init__()

def run_tests(self):
from tests.runner import main

test_labels = self.test_labels.split()
djtest_args = self.djtest_args.split()
main(['runner.py', 'test'] + test_labels + djtest_args)
Empty file added python/tests/__init__.py
Empty file.
28 changes: 28 additions & 0 deletions python/tests/runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import os
import sys
import glob


base = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))

# add test eggs to path
eggs = os.path.join(base, "*.egg")
sys.path += glob.glob(eggs)

# also add parent directory to path (to find tests)
pkg_base = os.path.join(base, 'python')
sys.path.append(pkg_base)

os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings'


def main(argv):
from django.core.management import execute_from_command_line
execute_from_command_line(argv)


if __name__ == '__main__':
args = sys.argv
args.insert(1, 'test')

main(args)
Loading