├── .bowerrc
├── .gitignore
├── .pep8
├── Dockerfile
├── Gruntfile.coffee
├── Makefile
├── Readme.markdown
├── bower.json
├── docker-compose.yml
├── example
├── __init__.py
├── api
│ ├── __init__.py
│ ├── admin.py
│ ├── api.py
│ ├── auth.py
│ ├── management
│ │ ├── __init__.py
│ │ └── commands
│ │ │ ├── __init__.py
│ │ │ ├── create_photos.py
│ │ │ ├── create_posts.py
│ │ │ └── create_users.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_auto_20150525_1522.py
│ │ └── __init__.py
│ ├── models.py
│ ├── permissions.py
│ ├── serializers.py
│ ├── templates
│ │ ├── base.html
│ │ ├── basic.html
│ │ ├── editor.html
│ │ ├── index.html
│ │ ├── manage.html
│ │ ├── photos.html
│ │ ├── playground.html
│ │ ├── resource.html
│ │ └── static.html
│ └── urls.py
├── assets
│ └── .gitignore
├── media
│ └── samples
│ │ ├── boston.jpg
│ │ ├── costa_rican_frog.jpg
│ │ └── parakeet.jpg
├── settings.py
├── src
│ └── js
│ │ ├── app.basic.coffee
│ │ ├── app.editor.coffee
│ │ ├── app.manage.coffee
│ │ ├── app.photos.coffee
│ │ ├── app.playground.coffee
│ │ ├── app.resource.coffee
│ │ ├── app.static.coffee
│ │ ├── app.update.coffee
│ │ └── service
│ │ ├── api.coffee
│ │ └── playground.coffee
├── urls.py
└── wsgi.py
├── manage.py
├── package.json
├── requirements.txt
├── scripts
└── create_db.sh
└── setup.py
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "example/assets"
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *~
2 | .DS_Store
3 | *.csv
4 | *.pyc
5 | *.swp
6 | *.egg-info
7 | build
8 | dist
9 | .coverage
10 | nosetests.xml
11 | lettucetests.xml
12 | coverage.xml
13 | ghostdriver.log
14 | .harvest
15 | pep8.report
16 | .env
17 | *.sqlite
18 | node_modules
19 | .vagrant
20 |
--------------------------------------------------------------------------------
/.pep8:
--------------------------------------------------------------------------------
1 | [pep8]
2 |
3 | # List of PEP8 Errors and Warnings to Ignore
4 | # E501 line too long (82 > 79 characters)
5 |
6 | # Most of the Indentation continuation rules are ignored (short of the mixed spaces and tabs)
7 |
8 | # E121 continuation line indentation is not a multiple of four
9 | # E122 continuation line missing indentation or outdented
10 | # E123 closing bracket does not match indentation of opening bracket’s line
11 | # E124 closing bracket does not match visual indentation
12 | # E125 continuation line does not distinguish itself from next logical line
13 | # E126 continuation line over-indented for hanging indent
14 | # E127 continuation line over-indented for visual indent
15 | # E128 continuation line under-indented for visual indent
16 |
17 | # Whitespace
18 | # E261 at least two spaces before inline comment
19 |
20 | ignore = E501,E12,E261
21 |
22 | exclude = migrations
23 |
24 | [flake8]
25 |
26 | # List of PEP8 Errors and Warnings to Ignore
27 | # E501 line too long (82 > 79 characters)
28 |
29 | # Most of the Indentation continuation rules are ignored (short of the mixed spaces and tabs)
30 |
31 | # E121 continuation line indentation is not a multiple of four
32 | # E122 continuation line missing indentation or outdented
33 | # E123 closing bracket does not match indentation of opening bracket’s line
34 | # E124 closing bracket does not match visual indentation
35 | # E125 continuation line does not distinguish itself from next logical line
36 | # E126 continuation line over-indented for hanging indent
37 | # E127 continuation line over-indented for visual indent
38 | # E128 continuation line under-indented for visual indent
39 |
40 | # Whitespace
41 | # E261 at least two spaces before inline comment
42 |
43 |
44 | # PyFlakes
45 | # F403 unable to detect undefined names (from whatever import *)
46 |
47 | ignore = E501,E12,E261,F403
48 |
49 | exclude = migrations
50 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:latest
2 |
3 | ENV DEBIAN_FRONTEND=noninteractive
4 |
5 | RUN apt-get update && apt-get install -y curl gnupg
6 | RUN curl -sL https://deb.nodesource.com/setup_11.x | bash -
7 |
8 | RUN apt-get update && apt-get install -y \
9 | build-essential git htop tmux pv \
10 | python3 python3-pip python3-dev libjpeg-dev zlib1g-dev \
11 | nodejs
12 |
13 | RUN npm install -g grunt-cli bower
14 |
15 | ADD requirements.txt /src/
16 | WORKDIR /src
17 |
18 | RUN pip3 install -r requirements.txt
19 |
20 | ADD package.json bower.json .bowerrc /src/
21 | RUN npm install
22 |
23 | RUN bower install --allow-root
24 |
25 | ADD setup.py manage.py Makefile /src/
26 | ADD example /src/example
27 | ADD scripts /src/scripts
28 | RUN python3 setup.py develop
29 |
30 | ADD Gruntfile.coffee /src/
31 | RUN grunt
32 |
33 | RUN make create_database
34 | RUN make make_fixtures
35 |
36 | EXPOSE 80
37 |
38 | CMD ["./manage.py", "runserver", "0.0.0.0:80"]
39 |
--------------------------------------------------------------------------------
/Gruntfile.coffee:
--------------------------------------------------------------------------------
1 | module.exports = (grunt) ->
2 | grunt.initConfig(
3 | pkg: grunt.file.readJSON('package.json')
4 | coffee:
5 | files:
6 | src: ['example/src/js/**/*.coffee']
7 | dest: 'example/assets/js/script.js'
8 | )
9 |
10 | grunt.loadNpmTasks('grunt-contrib-coffee')
11 |
12 | grunt.registerTask('default', ['coffee'])
13 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | clean:
2 | rm -f example.sqlite
3 |
4 | create_database:
5 | ./manage.py makemigrations --noinput
6 | ./manage.py migrate --noinput
7 | ./manage.py createsuperuser --username=root --email=root@example.com --noinput
8 |
9 | make_fixtures:
10 | ./manage.py create_users
11 | ./manage.py create_posts
12 | ./manage.py create_photos
13 |
14 | all: clean create_database make_fixtures
15 |
--------------------------------------------------------------------------------
/Readme.markdown:
--------------------------------------------------------------------------------
1 | # Django API with Django Rest Framework and AngularJS-Resource
2 |
3 | This sample project is the companion of a [blog post](http://kevinastone.github.io/getting-started-with-django-rest-framework-and-angularjs.html) on how to get started with Django Rest Framework and AngularJS.
4 |
5 | ## Dependencies
6 |
7 | To setup and run the sample code, you're going to need `npm` from NodeJS available to install the frontend code.
8 |
9 | ## Setup
10 |
11 | ### Docker
12 |
13 | If you have a docker host, you can simply use `docker-compose` to build the example, then open [http://localhost](http://localhost):
14 |
15 | ```
16 | docker-compose up
17 | ```
18 |
19 | ### Manual
20 |
21 | You're encouraged to setup a `virtualenv` to work in prior to configuring the dependencies.
22 |
23 | 1. Install Python Requirements
24 |
25 | pip install -r requirements.txt
26 | python setup.py develop
27 |
28 | 2. Install Bower + Grunt
29 |
30 | npm install -g grunt-cli bower
31 |
32 | 3. Install Assets
33 |
34 | npm install
35 | bower install
36 |
37 | 4. Compile Assets
38 |
39 | grunt
40 |
41 | 5. Setup the Database
42 |
43 | make create_database; make make_fixtures
44 |
45 | 6. Run the Server
46 |
47 | ./manage.py runserver
48 |
49 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "angular": "1.2.13",
6 | "angular-resource": "1.2.13",
7 | "underscore": "",
8 | "bootstrap": ">=3.0"
9 | },
10 | "private": true
11 | }
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | web:
4 | build: .
5 | ports:
6 | - "80:80"
7 |
--------------------------------------------------------------------------------
/example/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinastone/django-api-rest-and-angular/74501d9953186b0c02afdcaf917fbd64266c1b83/example/__init__.py
--------------------------------------------------------------------------------
/example/api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinastone/django-api-rest-and-angular/74501d9953186b0c02afdcaf917fbd64266c1b83/example/api/__init__.py
--------------------------------------------------------------------------------
/example/api/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 |
3 | from .models import User, Post, Photo
4 |
5 |
6 | admin.site.register(User)
7 | admin.site.register(Post)
8 | admin.site.register(Photo)
9 |
--------------------------------------------------------------------------------
/example/api/api.py:
--------------------------------------------------------------------------------
1 | from rest_framework import generics, permissions
2 |
3 |
4 | from .serializers import UserSerializer, PostSerializer, PhotoSerializer
5 | from .models import User, Post, Photo
6 | from .permissions import PostAuthorCanEditPermission
7 |
8 |
9 | class UserMixin(object):
10 | model = User
11 | queryset = User.objects.all()
12 | serializer_class = UserSerializer
13 |
14 |
15 | class UserList(UserMixin, generics.ListAPIView):
16 | permission_classes = [
17 | permissions.AllowAny
18 | ]
19 |
20 |
21 | class UserDetail(UserMixin, generics.RetrieveAPIView):
22 | lookup_field = 'username'
23 |
24 |
25 | class PostMixin(object):
26 | model = Post
27 | queryset = Post.objects.all()
28 | serializer_class = PostSerializer
29 | permission_classes = [
30 | PostAuthorCanEditPermission
31 | ]
32 |
33 | def perform_create(self, serializer):
34 | """Force author to the current user on save"""
35 | serializer.save(author=self.request.user)
36 |
37 |
38 | class PostList(PostMixin, generics.ListCreateAPIView):
39 | pass
40 |
41 |
42 | class PostDetail(PostMixin, generics.RetrieveUpdateDestroyAPIView):
43 | pass
44 |
45 |
46 | class UserPostList(generics.ListAPIView):
47 | model = Post
48 | queryset = Post.objects.all()
49 | serializer_class = PostSerializer
50 |
51 | def get_queryset(self):
52 | queryset = super(UserPostList, self).get_queryset()
53 | return queryset.filter(author__username=self.kwargs.get('username'))
54 |
55 |
56 | class PhotoMixin(object):
57 | model = Photo
58 | queryset = Photo.objects.all()
59 | serializer_class = PhotoSerializer
60 |
61 |
62 | class PhotoList(PhotoMixin, generics.ListCreateAPIView):
63 | permission_classes = [
64 | permissions.AllowAny
65 | ]
66 |
67 |
68 | class PhotoDetail(PhotoMixin, generics.RetrieveUpdateDestroyAPIView):
69 | permission_classes = [
70 | permissions.AllowAny
71 | ]
72 |
73 |
74 | class PostPhotoList(generics.ListAPIView):
75 | model = Photo
76 | queryset = Photo.objects.all()
77 | serializer_class = PhotoSerializer
78 |
79 | def get_queryset(self):
80 | queryset = super(PostPhotoList, self).get_queryset()
81 | return queryset.filter(post__pk=self.kwargs.get('pk'))
82 |
--------------------------------------------------------------------------------
/example/api/auth.py:
--------------------------------------------------------------------------------
1 | from .models import User
2 |
3 |
4 | class AlwaysRootBackend(object):
5 | def authenticate(self, *args, **kwargs):
6 | """Always returns the `root` user. DO NOT USE THIS IN PRODUCTION!"""
7 | return User.objects.get(username='root')
8 |
9 | def get_user(self, user_id):
10 | return User.objects.get(username='root')
11 |
--------------------------------------------------------------------------------
/example/api/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinastone/django-api-rest-and-angular/74501d9953186b0c02afdcaf917fbd64266c1b83/example/api/management/__init__.py
--------------------------------------------------------------------------------
/example/api/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinastone/django-api-rest-and-angular/74501d9953186b0c02afdcaf917fbd64266c1b83/example/api/management/commands/__init__.py
--------------------------------------------------------------------------------
/example/api/management/commands/create_photos.py:
--------------------------------------------------------------------------------
1 | import os
2 | import os.path
3 |
4 | from django.core.management.base import BaseCommand
5 | from django.conf import settings
6 |
7 | from example.api.models import Photo, Post
8 |
9 |
10 | class Command(BaseCommand):
11 | sample_dir = 'samples'
12 |
13 | def handle(self, *args, **options):
14 | sample_images = [os.path.join(self.sample_dir, fn) for fn in os.listdir(os.path.join(settings.MEDIA_ROOT, self.sample_dir))]
15 |
16 | posts = Post.objects.all()
17 |
18 | for i, image in enumerate(sample_images):
19 | Photo.objects.create(post=posts[i % posts.count()], image=image)
20 |
--------------------------------------------------------------------------------
/example/api/management/commands/create_posts.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 |
3 | from example.api.models import Post, User
4 |
5 | bodies = ['This is a great post', 'Another thing I wanted to share']
6 |
7 |
8 | class Command(BaseCommand):
9 | def handle(self, *args, **options):
10 | users = User.objects.all()
11 |
12 | for i, body in enumerate(bodies):
13 | Post.objects.create(author=users[i % users.count()], title="Title #{}".format(i + 1), body=body)
14 |
--------------------------------------------------------------------------------
/example/api/management/commands/create_users.py:
--------------------------------------------------------------------------------
1 | from django.core.management.base import BaseCommand
2 |
3 | from example.api.models import User
4 |
5 |
6 | class Command(BaseCommand):
7 | def handle(self, *args, **options):
8 | users = ['Bob', 'Sally', 'Joe', 'Rachel']
9 | for user in users:
10 | username = user.lower()
11 | User.objects.create(username=username, email="{}@example.com".format(username), first_name=user)
12 |
--------------------------------------------------------------------------------
/example/api/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | import django.utils.timezone
6 | from django.conf import settings
7 | import django.core.validators
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | dependencies = [
13 | ('auth', '0001_initial'),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='User',
19 | fields=[
20 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
21 | ('password', models.CharField(max_length=128, verbose_name='password')),
22 | ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
23 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
24 | ('username', models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=30, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])),
25 | ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
26 | ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
27 | ('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)),
28 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
29 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
30 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
31 | ('followers', models.ManyToManyField(related_name='followees', to=settings.AUTH_USER_MODEL)),
32 | ('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups')),
33 | ('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
34 | ],
35 | options={
36 | 'abstract': False,
37 | 'verbose_name': 'user',
38 | 'verbose_name_plural': 'users',
39 | },
40 | bases=(models.Model,),
41 | ),
42 | migrations.CreateModel(
43 | name='Photo',
44 | fields=[
45 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
46 | ('image', models.ImageField(upload_to='%Y/%m/%d')),
47 | ],
48 | options={
49 | },
50 | bases=(models.Model,),
51 | ),
52 | migrations.CreateModel(
53 | name='Post',
54 | fields=[
55 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
56 | ('title', models.CharField(max_length=255)),
57 | ('body', models.TextField(null=True, blank=True)),
58 | ('author', models.ForeignKey(related_name='posts', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
59 | ],
60 | options={
61 | },
62 | bases=(models.Model,),
63 | ),
64 | migrations.AddField(
65 | model_name='photo',
66 | name='post',
67 | field=models.ForeignKey(related_name='photos', to='api.Post', on_delete=models.CASCADE),
68 | preserve_default=True,
69 | ),
70 | ]
71 |
--------------------------------------------------------------------------------
/example/api/migrations/0002_auto_20150525_1522.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 | import django.core.validators
6 | import django.contrib.auth.models
7 |
8 |
9 | class Migration(migrations.Migration):
10 |
11 | dependencies = [
12 | ('api', '0001_initial'),
13 | ]
14 |
15 | operations = [
16 | migrations.AlterModelManagers(
17 | name='user',
18 | managers=[
19 | ('objects', django.contrib.auth.models.UserManager()),
20 | ],
21 | ),
22 | migrations.AlterField(
23 | model_name='user',
24 | name='email',
25 | field=models.EmailField(max_length=254, verbose_name='email address', blank=True),
26 | ),
27 | migrations.AlterField(
28 | model_name='user',
29 | name='groups',
30 | field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'),
31 | ),
32 | migrations.AlterField(
33 | model_name='user',
34 | name='last_login',
35 | field=models.DateTimeField(null=True, verbose_name='last login', blank=True),
36 | ),
37 | migrations.AlterField(
38 | model_name='user',
39 | name='username',
40 | field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username'),
41 | ),
42 | ]
43 |
--------------------------------------------------------------------------------
/example/api/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinastone/django-api-rest-and-angular/74501d9953186b0c02afdcaf917fbd64266c1b83/example/api/migrations/__init__.py
--------------------------------------------------------------------------------
/example/api/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | from django.contrib.auth.models import AbstractUser
4 |
5 |
6 | class User(AbstractUser):
7 | followers = models.ManyToManyField('self', related_name='followees', symmetrical=False)
8 |
9 |
10 | class Post(models.Model):
11 | author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
12 | title = models.CharField(max_length=255)
13 | body = models.TextField(blank=True, null=True)
14 |
15 |
16 | class Photo(models.Model):
17 | post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='photos')
18 | image = models.ImageField(upload_to="%Y/%m/%d")
19 |
--------------------------------------------------------------------------------
/example/api/permissions.py:
--------------------------------------------------------------------------------
1 | from rest_framework import permissions
2 |
3 |
4 | class SafeMethodsOnlyPermission(permissions.BasePermission):
5 | """Only can access non-destructive methods (like GET and HEAD)"""
6 | def has_permission(self, request, view):
7 | return self.has_object_permission(request, view)
8 |
9 | def has_object_permission(self, request, view, obj=None):
10 | return request.method in permissions.SAFE_METHODS
11 |
12 |
13 | class PostAuthorCanEditPermission(SafeMethodsOnlyPermission):
14 | """Allow everyone to list or view, but only the author can modify existing instances"""
15 | def has_object_permission(self, request, view, obj=None):
16 | if obj is None:
17 | # Either a list or a create, so no author
18 | can_edit = True
19 | else:
20 | can_edit = request.user == obj.author
21 | return can_edit or super(PostAuthorCanEditPermission, self).has_object_permission(request, view, obj)
22 |
--------------------------------------------------------------------------------
/example/api/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 |
3 | from .models import User, Post, Photo
4 |
5 |
6 | class UserSerializer(serializers.ModelSerializer):
7 | posts = serializers.HyperlinkedIdentityField(view_name='userpost-list', lookup_field='username')
8 |
9 | class Meta:
10 | model = User
11 | fields = ('id', 'username', 'first_name', 'last_name', 'posts', )
12 |
13 |
14 | class PostSerializer(serializers.ModelSerializer):
15 | author = UserSerializer(required=False)
16 | photos = serializers.HyperlinkedIdentityField(view_name='postphoto-list')
17 | # author = serializers.HyperlinkedRelatedField(view_name='user-detail', lookup_field='username')
18 |
19 | def get_validation_exclusions(self, *args, **kwargs):
20 | # Need to exclude `user` since we'll add that later based off the request
21 | exclusions = super(PostSerializer, self).get_validation_exclusions(*args, **kwargs)
22 | return exclusions + ['author']
23 |
24 | class Meta:
25 | model = Post
26 | fields = '__all__'
27 |
28 |
29 | class PhotoSerializer(serializers.ModelSerializer):
30 | image = serializers.ReadOnlyField(source='image.url')
31 |
32 | class Meta:
33 | model = Photo
34 | fields = '__all__'
35 |
--------------------------------------------------------------------------------
/example/api/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 |
6 |
18 |
19 |
20 |
21 |
{% block content %}
22 |
23 |
{% block post_header %}
24 | {% verbatim %}
25 |
{{ post.title }} {{ post.author.username }}
26 | {% endverbatim %}
27 | {% endblock %}
28 | {% verbatim %}
29 |
{{ post.body }}
30 |
31 |
32 |
33 | {% endverbatim %}
34 |
35 | {% endblock %}
36 |
37 | {% block js %}
38 |
39 |
40 |
41 |
42 | {% endblock %}
43 |
44 |
--------------------------------------------------------------------------------
/example/api/templates/basic.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block ng_app %}example.app.basic{% endblock %}
4 |
--------------------------------------------------------------------------------
/example/api/templates/editor.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block ng_app %}example.app.editor{% endblock %}
4 |
5 | {% block content %}
6 | {% verbatim %}
7 |
8 |
Create a New Post
9 |
21 |
22 | {% endverbatim %}
23 | {{ block.super }}
24 | {% endblock %}
25 |
26 | {% block js %}
27 | {{ block.super }}
28 |
35 | {% endblock %}
36 |
--------------------------------------------------------------------------------
/example/api/templates/index.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 |
6 | - Hard Coded Example
7 | - Basic Ajax Example
8 | - Angular Resource Example
9 | - Photos Example
10 | - Editor Example
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/api/templates/manage.html:
--------------------------------------------------------------------------------
1 | {% extends 'editor.html' %}
2 |
3 | {% block ng_app %}example.app.manage{% endblock %}
4 |
5 | {% block post_header %}
6 |
7 | {{ block.super }}
8 | {% endblock %}
9 |
10 | {% block js %}
11 | {{ block.super }}
12 |
22 | {% endblock %}
23 |
--------------------------------------------------------------------------------
/example/api/templates/photos.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block ng_app %}example.app.photos{% endblock %}
4 |
--------------------------------------------------------------------------------
/example/api/templates/playground.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 | {% verbatim %}
6 |
7 |
8 | - {{ user.username }}
9 |
10 | New User
11 |
12 | {% endverbatim %}
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/example/api/templates/resource.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block ng_app %}example.app.resource{% endblock %}
4 |
--------------------------------------------------------------------------------
/example/api/templates/static.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block ng_app %}example.app.static{% endblock %}
4 |
--------------------------------------------------------------------------------
/example/api/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url, include
2 |
3 | from .api import UserList, UserDetail
4 | from .api import PostList, PostDetail, UserPostList
5 | from .api import PhotoList, PhotoDetail, PostPhotoList
6 |
7 | user_urls = [
8 | url(r'^/(?P[0-9a-zA-Z_-]+)/posts$', UserPostList.as_view(), name='userpost-list'),
9 | url(r'^/(?P[0-9a-zA-Z_-]+)$', UserDetail.as_view(), name='user-detail'),
10 | url(r'^$', UserList.as_view(), name='user-list')
11 | ]
12 |
13 | post_urls = [
14 | url(r'^/(?P\d+)/photos$', PostPhotoList.as_view(), name='postphoto-list'),
15 | url(r'^/(?P\d+)$', PostDetail.as_view(), name='post-detail'),
16 | url(r'^$', PostList.as_view(), name='post-list')
17 | ]
18 |
19 | photo_urls = [
20 | url(r'^/(?P\d+)$', PhotoDetail.as_view(), name='photo-detail'),
21 | url(r'^$', PhotoList.as_view(), name='photo-list')
22 | ]
23 |
24 | urlpatterns = [
25 | url(r'^users', include(user_urls)),
26 | url(r'^posts', include(post_urls)),
27 | url(r'^photos', include(photo_urls)),
28 | ]
29 |
--------------------------------------------------------------------------------
/example/assets/.gitignore:
--------------------------------------------------------------------------------
1 | !.gitignore
2 | *
--------------------------------------------------------------------------------
/example/media/samples/boston.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinastone/django-api-rest-and-angular/74501d9953186b0c02afdcaf917fbd64266c1b83/example/media/samples/boston.jpg
--------------------------------------------------------------------------------
/example/media/samples/costa_rican_frog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinastone/django-api-rest-and-angular/74501d9953186b0c02afdcaf917fbd64266c1b83/example/media/samples/costa_rican_frog.jpg
--------------------------------------------------------------------------------
/example/media/samples/parakeet.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinastone/django-api-rest-and-angular/74501d9953186b0c02afdcaf917fbd64266c1b83/example/media/samples/parakeet.jpg
--------------------------------------------------------------------------------
/example/settings.py:
--------------------------------------------------------------------------------
1 | # Django settings for example project.
2 |
3 | DEBUG = True
4 |
5 | ADMINS = (
6 | # ('Your Name', 'your_email@example.com'),
7 | )
8 |
9 | MANAGERS = ADMINS
10 |
11 | DATABASES = {
12 | 'default': {
13 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
14 | 'NAME': 'example.sqlite', # Or path to database file if using sqlite3.
15 | # The following settings are not used with sqlite3:
16 | 'USER': 'example',
17 | 'PASSWORD': '',
18 | 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
19 | 'PORT': '', # Set to empty string for default.
20 | }
21 | }
22 |
23 | # Hosts/domain names that are valid for this site; required if DEBUG is False
24 | # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
25 | ALLOWED_HOSTS = ['example.com','localhost','127.0.0.1']
26 |
27 | # Local time zone for this installation. Choices can be found here:
28 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
29 | # although not all choices may be available on all operating systems.
30 | # In a Windows environment this must be set to your system time zone.
31 | TIME_ZONE = 'America/Los_Angeles'
32 |
33 | # Language code for this installation. All choices can be found here:
34 | # http://www.i18nguy.com/unicode/language-identifiers.html
35 | LANGUAGE_CODE = 'en-us'
36 |
37 | SITE_ID = 1
38 |
39 | # If you set this to False, Django will make some optimizations so as not
40 | # to load the internationalization machinery.
41 | USE_I18N = True
42 |
43 | # If you set this to False, Django will not format dates, numbers and
44 | # calendars according to the current locale.
45 | USE_L10N = True
46 |
47 | # If you set this to False, Django will not use timezone-aware datetimes.
48 | USE_TZ = True
49 |
50 | # Absolute filesystem path to the directory that will hold user-uploaded files.
51 | # Example: "/var/www/example.com/media/"
52 | import os.path
53 | MEDIA_ROOT = os.path.normpath(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'media'))
54 |
55 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a
56 | # trailing slash.
57 | # Examples: "http://example.com/media/", "http://media.example.com/"
58 | MEDIA_URL = '/media/'
59 |
60 | # Absolute path to the directory static files should be collected to.
61 | # Don't put anything in this directory yourself; store your static files
62 | # in apps' "static/" subdirectories and in STATICFILES_DIRS.
63 | # Example: "/var/www/example.com/static/"
64 | import os.path
65 | STATIC_ROOT = os.path.normpath(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'static'))
66 |
67 | # URL prefix for static files.
68 | # Example: "http://example.com/static/", "http://static.example.com/"
69 | STATIC_URL = '/static/'
70 |
71 | # Additional locations of static files
72 | STATICFILES_DIRS = (
73 | os.path.normpath(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'assets')),
74 | # Put strings here, like "/home/html/static" or "C:/www/django/static".
75 | # Always use forward slashes, even on Windows.
76 | # Don't forget to use absolute paths, not relative paths.
77 | )
78 |
79 | # List of finder classes that know how to find static files in
80 | # various locations.
81 | STATICFILES_FINDERS = (
82 | 'django.contrib.staticfiles.finders.FileSystemFinder',
83 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
84 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
85 | )
86 |
87 | # Make this unique, and don't share it with anybody.
88 | SECRET_KEY = 'm7^i&pc&^*nx*3y9ui%c68c(lcor-^3_-ma+qjk!5lz7c!wrts'
89 |
90 | TEMPLATES = [{
91 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
92 | 'DIRS': [
93 | # insert your TEMPLATE_DIRS here
94 | ],
95 | 'APP_DIRS': True,
96 | 'OPTIONS': {
97 | 'debug': DEBUG,
98 | 'context_processors': [
99 | # Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this
100 | # list if you haven't customized them:
101 | 'django.contrib.auth.context_processors.auth',
102 | 'django.template.context_processors.debug',
103 | 'django.template.context_processors.i18n',
104 | 'django.template.context_processors.media',
105 | 'django.template.context_processors.static',
106 | 'django.template.context_processors.tz',
107 | 'django.contrib.messages.context_processors.messages',
108 | ],
109 | },
110 | }]
111 |
112 | MIDDLEWARE = (
113 | 'django.middleware.common.CommonMiddleware',
114 | 'django.contrib.sessions.middleware.SessionMiddleware',
115 | 'django.middleware.csrf.CsrfViewMiddleware',
116 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
117 | 'django.contrib.messages.middleware.MessageMiddleware',
118 | # Uncomment the next line for simple clickjacking protection:
119 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
120 | )
121 |
122 | ROOT_URLCONF = 'example.urls'
123 |
124 | # Python dotted path to the WSGI application used by Django's runserver.
125 | WSGI_APPLICATION = 'example.wsgi.application'
126 |
127 | INSTALLED_APPS = (
128 | 'django.contrib.auth',
129 | 'django.contrib.contenttypes',
130 | 'django.contrib.sessions',
131 | 'django.contrib.sites',
132 | 'django.contrib.messages',
133 | 'django.contrib.staticfiles',
134 | 'django_extensions',
135 | 'rest_framework',
136 | 'example.api',
137 | # Uncomment the next line to enable the admin:
138 | 'django.contrib.admin',
139 | # Uncomment the next line to enable admin documentation:
140 | # 'django.contrib.admindocs',
141 | )
142 |
143 | # A sample logging configuration. The only tangible logging
144 | # performed by this configuration is to send an email to
145 | # the site admins on every HTTP 500 error when DEBUG=False.
146 | # See http://docs.djangoproject.com/en/dev/topics/logging for
147 | # more details on how to customize your logging configuration.
148 | LOGGING = {
149 | 'version': 1,
150 | 'disable_existing_loggers': False,
151 | 'filters': {
152 | 'require_debug_false': {
153 | '()': 'django.utils.log.RequireDebugFalse'
154 | }
155 | },
156 | 'handlers': {
157 | 'mail_admins': {
158 | 'level': 'ERROR',
159 | 'filters': ['require_debug_false'],
160 | 'class': 'django.utils.log.AdminEmailHandler'
161 | }
162 | },
163 | 'loggers': {
164 | 'django.request': {
165 | 'handlers': ['mail_admins'],
166 | 'level': 'ERROR',
167 | 'propagate': True,
168 | },
169 | }
170 | }
171 |
172 | TEST_RUNNER = 'django.test.runner.DiscoverRunner'
173 |
174 | AUTH_USER_MODEL = 'api.User'
175 |
176 | # Don't complain about leading slashes in URL patterns
177 | SILENCED_SYSTEM_CHECKS = ['urls.W002']
178 |
179 | # !!!!!This is for demonstration only!!!!!
180 | AUTHENTICATION_BACKENDS = ['example.api.auth.AlwaysRootBackend']
181 |
--------------------------------------------------------------------------------
/example/src/js/app.basic.coffee:
--------------------------------------------------------------------------------
1 | app = angular.module 'example.app.basic', []
2 |
3 | app.controller 'AppController', ['$scope', '$http', ($scope, $http) ->
4 | $scope.posts = []
5 | $http.get('/api/posts').then (result) ->
6 | angular.forEach result.data, (item) ->
7 | $scope.posts.push item
8 | ]
9 |
--------------------------------------------------------------------------------
/example/src/js/app.editor.coffee:
--------------------------------------------------------------------------------
1 | app = angular.module 'example.app.editor', ['example.api', 'example.app.photos']
2 |
3 | app.controller 'EditController', ['$scope', 'Post', ($scope, Post) ->
4 |
5 | $scope.newPost = new Post()
6 | $scope.save = ->
7 | $scope.newPost.$save().then (result) ->
8 | $scope.posts.push result
9 | .then ->
10 | # Reset our editor to a new blank post
11 | $scope.newPost = new Post()
12 | .then ->
13 | # Clear any errors
14 | $scope.errors = null
15 | , (rejection) ->
16 | $scope.errors = rejection.data
17 | ]
18 |
--------------------------------------------------------------------------------
/example/src/js/app.manage.coffee:
--------------------------------------------------------------------------------
1 | app = angular.module 'example.app.manage', ['example.api', 'example.app.editor']
2 |
3 | app.controller 'DeleteController', ['$scope', 'AuthUser', ($scope, AuthUser) ->
4 | $scope.canDelete = (post) ->
5 | return post.author.username == AuthUser.username
6 |
7 | $scope.delete = (post) ->
8 | post.$delete()
9 | .then ->
10 | # Remove it from the list on success
11 | idx = $scope.posts.indexOf(post)
12 | $scope.posts.splice(idx, 1)
13 | ]
14 |
--------------------------------------------------------------------------------
/example/src/js/app.photos.coffee:
--------------------------------------------------------------------------------
1 | app = angular.module 'example.app.photos', ['example.api']
2 |
3 | app.controller 'AppController', ['$scope', 'Post', 'PostPhoto', ($scope, Post, PostPhoto) ->
4 | $scope.photos = {}
5 | $scope.posts = Post.query()
6 | $scope.posts.$promise.then (results) ->
7 | # Load the photos
8 | angular.forEach results, (post) ->
9 | $scope.photos[post.id] = PostPhoto.query(post_id: post.id)
10 | ]
11 |
--------------------------------------------------------------------------------
/example/src/js/app.playground.coffee:
--------------------------------------------------------------------------------
1 | app = angular.module 'example.app.playground', ['example.api.playground']
2 |
3 | app.controller 'AppController', ['$scope', 'User', ($scope, User) ->
4 | $scope.users = []
5 | $scope.newUsername = ""
6 |
7 | $scope.loadUsers = ->
8 | # Reload the users
9 | User.query().$promise.then (results) ->
10 | $scope.users = results
11 |
12 | $scope.addUser = ->
13 | user = new User(username: $scope.newUsername)
14 | $scope.newUsername = ""
15 | user.$save()
16 | .then $scope.loadUsers
17 |
18 | $scope.deleteUser = (user) ->
19 | user.$delete()
20 | .then $scope.loadUsers
21 |
22 | $scope.loadUsers()
23 | ]
24 |
--------------------------------------------------------------------------------
/example/src/js/app.resource.coffee:
--------------------------------------------------------------------------------
1 | app = angular.module 'example.app.resource', ['example.api']
2 |
3 | app.controller 'AppController', ['$scope', 'Post', ($scope, Post) ->
4 | $scope.posts = Post.query()
5 | ]
6 |
--------------------------------------------------------------------------------
/example/src/js/app.static.coffee:
--------------------------------------------------------------------------------
1 | app = angular.module 'example.app.static', []
2 |
3 | app.controller 'AppController', ['$scope', '$http', ($scope, $http) ->
4 | $scope.posts = [
5 | author:
6 | username: 'Joe'
7 | title: 'Sample Post #1'
8 | body: 'This is the first sample post'
9 | ,
10 | author:
11 | username: 'Karen'
12 | title: 'Sample Post #2'
13 | body: 'This is another sample post'
14 | ]
15 | ]
16 |
--------------------------------------------------------------------------------
/example/src/js/app.update.coffee:
--------------------------------------------------------------------------------
1 | app = angular.module 'example.app.update', ['example.api']
2 |
3 | app.controller 'AppController', ['$scope', 'User', ($scope, User) ->
4 | $scope.users = []
5 | $scope.newUsername = ""
6 |
7 | $scope.loadUsers = ->
8 | # Reload the users
9 | User.query().$promise.then (results) ->
10 | $scope.users = results
11 |
12 | $scope.addUser = ->
13 | user = new User(username: $scope.newUsername)
14 | $scope.newUsername = ""
15 | user.$save()
16 | .then $scope.loadUsers
17 |
18 | $scope.deleteUser = (user) ->
19 | user.$delete()
20 | .then $scope.loadUsers
21 |
22 | $scope.loadUsers()
23 | ]
24 |
--------------------------------------------------------------------------------
/example/src/js/service/api.coffee:
--------------------------------------------------------------------------------
1 | app = angular.module 'example.api', ['ngResource']
2 |
3 | app.factory 'User', ['$resource', ($resource) ->
4 | $resource '/api/users/:username', username: '@username'
5 | ]
6 |
7 | app.factory 'Post', ['$resource', ($resource) ->
8 | $resource '/api/posts/:id', id: '@id'
9 | ]
10 |
11 | app.factory 'Photo', ['$resource', ($resource) ->
12 | $resource '/api/photos/:id', id: '@id'
13 | ]
14 |
15 | # And the nested resources
16 | app.factory 'UserPost', ['$resource', ($resource) ->
17 | $resource '/api/users/:username/posts/:id'
18 | ]
19 |
20 | app.factory 'PostPhoto', ['$resource', ($resource) ->
21 | $resource '/api/posts/:post_id/photos/:id'
22 | ]
23 |
--------------------------------------------------------------------------------
/example/src/js/service/playground.coffee:
--------------------------------------------------------------------------------
1 | app = angular.module 'example.api.playground', []
2 |
3 | app.factory 'User', ['$q', ($q) ->
4 | storage = {}
5 |
6 | class MockUser
7 | constructor: (params) ->
8 | for key, value of params
9 | @[key] = value
10 | @query: ->
11 | dfr = $q.defer()
12 | values = (val for key, val of storage)
13 | dfr.resolve(values)
14 | values.$promise = dfr.promise
15 | return values
16 | @save: (params) ->
17 | user = new @(params)
18 | user.$save()
19 | return user
20 | $save: ->
21 | storage[@username] = @
22 | dfr = $q.defer()
23 | dfr.resolve(@)
24 | return dfr.promise
25 | $delete: ->
26 | delete storage[@username]
27 | dfr = $q.defer()
28 | dfr.resolve()
29 | return dfr.promise
30 |
31 | for username in ['bob', 'sally', 'joe', 'rachel']
32 | user = new MockUser(username: username)
33 | storage[user.username] = user
34 |
35 | return MockUser
36 | ]
--------------------------------------------------------------------------------
/example/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import include, url
2 | from django.conf import settings
3 |
4 | from django.views.generic import TemplateView
5 |
6 | # Uncomment the next two lines to enable the admin:
7 | from django.contrib import admin
8 | admin.autodiscover()
9 |
10 |
11 | class SimpleStaticView(TemplateView):
12 | def get_template_names(self):
13 | return [self.kwargs.get('template_name') + ".html"]
14 |
15 | def get(self, request, *args, **kwargs):
16 | from django.contrib.auth import authenticate, login
17 | if request.user.is_anonymous:
18 | # Auto-login the User for Demonstration Purposes
19 | user = authenticate()
20 | login(request, user)
21 | return super(SimpleStaticView, self).get(request, *args, **kwargs)
22 |
23 |
24 | urlpatterns = [
25 | url(r'^api/', include('example.api.urls')),
26 | url(r'^admin/', admin.site.urls),
27 | url(r'^(?P\w+)$', SimpleStaticView.as_view(), name='example'),
28 | url(r'^$', TemplateView.as_view(template_name='index.html')),
29 | ]
30 |
31 | if settings.DEBUG:
32 | from django.views.static import serve
33 | urlpatterns += [
34 | url(r'^(?Pfavicon\..*)$', serve, {'document_root': settings.STATIC_ROOT}),
35 | url(r'^%s(?P.*)$' % settings.MEDIA_URL[1:], serve, {'document_root': settings.MEDIA_ROOT}),
36 | url(r'^%s(?P.*)$' % settings.STATIC_URL[1:], serve, dict(insecure=True)),
37 | ]
38 |
--------------------------------------------------------------------------------
/example/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for example project.
3 |
4 | This module contains the WSGI application used by Django's development server
5 | and any production WSGI deployments. It should expose a module-level variable
6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7 | this application via the ``WSGI_APPLICATION`` setting.
8 |
9 | Usually you will have the standard Django WSGI application here, but it also
10 | might make sense to replace the whole Django WSGI application with a custom one
11 | that later delegates to the Django one. For example, you could introduce WSGI
12 | middleware here, or combine a Django application with an application of another
13 | framework.
14 |
15 | """
16 | import os
17 |
18 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
19 | # if running multiple sites in the same mod_wsgi process. To fix this, use
20 | # mod_wsgi daemon mode with each site in its own daemon process, or use
21 | # os.environ["DJANGO_SETTINGS_MODULE"] = "example.settings"
22 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
23 |
24 | # This application object is used by any WSGI server configured to use this
25 | # file. This includes Django's development server, if the WSGI_APPLICATION
26 | # setting points here.
27 | from django.core.wsgi import get_wsgi_application
28 | application = get_wsgi_application()
29 |
30 | # Apply WSGI middleware here.
31 | # from helloworld.wsgi import HelloWorldApplication
32 | # application = HelloWorldApplication(application)
33 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
7 |
8 | from django.core.management import execute_from_command_line
9 |
10 | execute_from_command_line(sys.argv)
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "devDependencies": {
4 | "coffee-script": "",
5 | "grunt": "",
6 | "grunt-contrib-coffee": ""
7 | }
8 | }
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django>=2.0
2 | djangorestframework>=3.9
3 | pillow
4 |
5 | # Optional
6 | Werkzeug
7 | django-extensions
8 |
--------------------------------------------------------------------------------
/scripts/create_db.sh:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kevinastone/django-api-rest-and-angular/74501d9953186b0c02afdcaf917fbd64266c1b83/scripts/create_db.sh
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from setuptools import setup, find_packages
4 |
5 |
6 | # Shamelessly stolen (then modified) from https://github.com/cburgmer/pdfserver/blob/master/setup.py
7 | def parse_requirements(file_name):
8 | import re
9 | requirements = []
10 | for line in open(file_name, 'r').read().split('\n'):
11 | if re.match(r'(\s*#)|(\s*$)', line):
12 | continue
13 | # if re.match(r'\s*-e\s+', line):
14 | m = re.search(r"(git(?:\+\w{3})?|https?|svn)://.+#egg=(.*)$", line)
15 | if m:
16 | # FIXME: Can't install packages from source repos right now
17 | if 'http' in m.group(1):
18 | # Distutils can install Http served packages right now
19 | # FIXME: Skip this now
20 | # requirements.append(m.group(2))
21 | pass
22 | pass
23 | elif re.match(r'\s*-f\s+', line):
24 | pass
25 | elif re.match(r'\s*-i\s+', line):
26 | pass
27 | else:
28 | requirements.append(line)
29 |
30 | return requirements
31 |
32 |
33 | def parse_dependency_links(file_name):
34 | import re
35 | dependency_links = []
36 | for line in open(file_name, 'r').read().split('\n'):
37 | if re.match(r'\s*-[ef]\s+', line):
38 | dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line))
39 | continue
40 | m = re.search(r"((?:git(?:\+ssh)|http|svn)://.+#egg=.*)$", line)
41 | if m:
42 | dependency_links.append(m.group(1))
43 |
44 | return dependency_links
45 |
46 | params = dict(
47 | name='API Example',
48 | packages=find_packages(),
49 | install_requires=parse_requirements('requirements.txt'),
50 | dependency_links=parse_dependency_links('requirements.txt'),
51 | entry_points={
52 | 'console_scripts': [
53 | ]
54 | },
55 | )
56 |
57 | setup(**params)
58 |
--------------------------------------------------------------------------------