├── .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 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |

{{ name }}: {{ errs.join(', ') }}

20 |
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 |
  1. Hard Coded Example
  2. 7 |
  3. Basic Ajax Example
  4. 8 |
  5. Angular Resource Example
  6. 9 |
  7. Photos Example
  8. 10 |
  9. Editor Example
  10. 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 |
  1. {{ user.username }}
  2. 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 | --------------------------------------------------------------------------------