diff --git a/requirements.txt b/requirements.txt
index 31d5bc5..a9e33c7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,17 @@
+dj-database-url==0.3.0
+dj-static==0.0.6
Django==1.8.2
django-bootstrap3==5.4.0
django-debug-toolbar==1.3.0
django-extensions==1.5.5
+django-toolbelt==0.0.1
+djangorestframework==3.1.3
fake-factory==0.5.1
+gnureadline==6.3.3
+gunicorn==19.3.0
ipython==3.1.0
psycopg2==2.6.1
+six==1.9.0
+sqlparse==0.1.15
+static3==0.6.1
Werkzeug==0.10.4
diff --git a/urlybird/.idea/.name b/urlybird/.idea/.name
new file mode 100644
index 0000000..3aa37c8
--- /dev/null
+++ b/urlybird/.idea/.name
@@ -0,0 +1 @@
+urlybird
\ No newline at end of file
diff --git a/urlybird/.idea/misc.xml b/urlybird/.idea/misc.xml
new file mode 100644
index 0000000..77c6986
--- /dev/null
+++ b/urlybird/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/urlybird/.idea/modules.xml b/urlybird/.idea/modules.xml
new file mode 100644
index 0000000..be78f5b
--- /dev/null
+++ b/urlybird/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/urlybird/.idea/urlybird.iml b/urlybird/.idea/urlybird.iml
new file mode 100644
index 0000000..333a9fe
--- /dev/null
+++ b/urlybird/.idea/urlybird.iml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/urlybird/.idea/vcs.xml b/urlybird/.idea/vcs.xml
new file mode 100644
index 0000000..6564d52
--- /dev/null
+++ b/urlybird/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/urlybird/.idea/workspace.xml b/urlybird/.idea/workspace.xml
new file mode 100644
index 0000000..1668530
--- /dev/null
+++ b/urlybird/.idea/workspace.xml
@@ -0,0 +1,880 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1434647743579
+
+ 1434647743579
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/urlybird/Procfile b/urlybird/Procfile
new file mode 100644
index 0000000..da5bc49
--- /dev/null
+++ b/urlybird/Procfile
@@ -0,0 +1 @@
+web: gunicorn urlybird.wsgi --log-file -
\ No newline at end of file
diff --git a/urlybird/api/__init__.py b/urlybird/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/urlybird/api/admin.py b/urlybird/api/admin.py
new file mode 100644
index 0000000..8c38f3f
--- /dev/null
+++ b/urlybird/api/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/urlybird/api/migrations/__init__.py b/urlybird/api/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/urlybird/api/models.py b/urlybird/api/models.py
new file mode 100644
index 0000000..71a8362
--- /dev/null
+++ b/urlybird/api/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/urlybird/api/permissions.py b/urlybird/api/permissions.py
new file mode 100644
index 0000000..720fbb8
--- /dev/null
+++ b/urlybird/api/permissions.py
@@ -0,0 +1,25 @@
+from rest_framework import permissions
+
+
+class IsOwnerOrReadOnly(permissions.BasePermission):
+ def has_object_permission(self, request, view, obj):
+ if request.method in permissions.SAFE_METHODS:
+ return True
+ else:
+ return request.user == obj.user
+
+
+class OwnsRelatedBookmark(permissions.BasePermission):
+ def has_object_permission(self, request, view, obj):
+ return request.user == obj.bookmark.user
+
+
+class IsUser(permissions.BasePermission):
+ def has_permission(self, request, view):
+ if request.method == "POST":
+ return request.user.is_anonymous()
+ else:
+ return True
+
+ def has_object_permission(self, request, view, obj):
+ return request.user == obj
diff --git a/urlybird/api/serializers.py b/urlybird/api/serializers.py
new file mode 100644
index 0000000..a6c4692
--- /dev/null
+++ b/urlybird/api/serializers.py
@@ -0,0 +1,58 @@
+from rest_framework import serializers
+from urly.models import Bookmark, Click
+from rest_framework.fields import SerializerMethodField
+from rest_framework.reverse import reverse
+from django.contrib.auth.models import User
+from rest_framework import serializers
+
+
+class ClickSerializer(serializers.HyperlinkedModelSerializer):
+ user = serializers.PrimaryKeyRelatedField(read_only=True)
+
+ class Meta:
+ model = Click
+ fields = ('user', 'bookmark')
+
+
+class ClickWithBookmarkSerializer(serializers.HyperlinkedModelSerializer):
+ bookmark = serializers.HyperlinkedRelatedField(read_only=True,
+ view_name='click-detail')
+
+ class Meta:
+ model = Click
+ fields = ('id', 'url', 'bookmark')
+
+
+class BookmarkSerializer(serializers.HyperlinkedModelSerializer):
+ user = serializers.PrimaryKeyRelatedField(read_only=True)
+ clicks = ClickSerializer(many=True, read_only=True)
+ click_count = serializers.IntegerField(read_only=True)
+ _links = SerializerMethodField()
+
+ def get__links(self, obj):
+ links = {
+ "clicks": reverse('click-list', kwargs=dict(bookmark_pk=obj.pk),
+ request=self.context.get('request'))}
+ return links
+
+ class Meta:
+ model = Bookmark
+ fields = ('id', 'url', 'title', 'description', 'longurl', 'shorturl', 'user', 'clicks', 'click_count', '_links')
+
+
+class UserSerializer(serializers.HyperlinkedModelSerializer):
+ class Meta:
+ model = User
+ fields = ('url', 'username', 'password', 'email')
+ write_only_fields = ('password',)
+
+ def create(self, validated_data):
+ user = User.objects.create(
+ username=validated_data['username'],
+ email=validated_data['email']
+ )
+
+ user.set_password(validated_data['password'])
+ user.save()
+
+ return user
diff --git a/urlybird/api/tests.py b/urlybird/api/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/urlybird/api/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/urlybird/api/views.py b/urlybird/api/views.py
new file mode 100644
index 0000000..cfa8e23
--- /dev/null
+++ b/urlybird/api/views.py
@@ -0,0 +1,58 @@
+from urly.models import Bookmark, Click
+from api.serializers import BookmarkSerializer, ClickSerializer, ClickWithBookmarkSerializer, UserSerializer
+from rest_framework import viewsets, permissions, generics, filters
+from api.permissions import IsOwnerOrReadOnly, IsUser
+from rest_framework.exceptions import PermissionDenied
+from django.db.models import Count
+from django.contrib.auth.models import User
+
+
+class BookmarkViewSet(viewsets.ModelViewSet):
+ serializer_class = BookmarkSerializer
+ permission_classes = (permissions.IsAuthenticated,
+ IsOwnerOrReadOnly)
+
+ def get_queryset(self):
+ return Bookmark.objects.filter(user=self.request.user).annotate(
+ click_count=Count('click', distinct=True))
+
+ def perform_create(self, serializer):
+ serializer.save(user=self.request.user)
+
+
+class ClickViewSet(viewsets.ModelViewSet):
+ queryset = Click.objects.all()
+ serializer_class = ClickSerializer
+ permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
+
+ def perform_create(self, serializer):
+ serializer.save(user=self.request.user)
+
+
+class ClickCreateView(generics.ListCreateAPIView):
+ permission_classes = (permissions.IsAuthenticated,)
+ serializer_class = ClickSerializer
+
+ def initial(self, request, *args, **kwargs):
+ self.bookmark = Bookmark.objects.get(pk=kwargs['bookmark_pk'])
+ super().initial(request, *args, **kwargs)
+
+ def get_queryset(self):
+ return self.bookmark.click_set.all()
+
+ def perform_create(self, serializer):
+ if self.request.user != self.bookmark.user:
+ raise PermissionDenied
+ serializer.save(bookmark=self.bookmark)
+
+
+class ClickDetailView(generics.RetrieveUpdateDestroyAPIView):
+ permission_classes = (permissions.IsAuthenticated,)
+ serializer_class = ClickWithBookmarkSerializer
+ queryset = Click.objects.all()
+
+
+class UserViewSet(viewsets.ModelViewSet):
+ permission_classes = (IsUser,)
+ queryset = User.objects.all()
+ serializer_class = UserSerializer
diff --git a/urlybird/manage.py b/urlybird/manage.py
new file mode 100755
index 0000000..11b1039
--- /dev/null
+++ b/urlybird/manage.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "urlybird.settings")
+
+ from django.core.management import execute_from_command_line
+
+ execute_from_command_line(sys.argv)
diff --git a/urlybird/runtime.txt b/urlybird/runtime.txt
new file mode 100644
index 0000000..d0d96e1
--- /dev/null
+++ b/urlybird/runtime.txt
@@ -0,0 +1 @@
+python-3.4.3
\ No newline at end of file
diff --git a/urlybird/static/.gitkeep b/urlybird/static/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/urlybird/urly/__init__.py b/urlybird/urly/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/urlybird/urly/admin.py b/urlybird/urly/admin.py
new file mode 100644
index 0000000..27d84b3
--- /dev/null
+++ b/urlybird/urly/admin.py
@@ -0,0 +1,17 @@
+from django.contrib import admin
+from .models import Bookmark, Click
+
+# Register your models here.
+class BookmarkAdmin(admin.ModelAdmin):
+ fields = ['user', 'title', 'description', 'longurl', 'shorturl', 'count']
+ list_display = ['title', 'longurl', 'shorturl']
+
+
+class ClickAdmin(admin.ModelAdmin):
+ class Meta:
+ fields = ['bookmark', 'timestamp']
+ list_display = ['bookmark', 'timestamp']
+
+
+admin.site.register(Bookmark, BookmarkAdmin)
+admin.site.register(Click, ClickAdmin)
diff --git a/urlybird/urly/forms.py b/urlybird/urly/forms.py
new file mode 100644
index 0000000..970c9ac
--- /dev/null
+++ b/urlybird/urly/forms.py
@@ -0,0 +1,21 @@
+from django import forms
+from django.contrib.auth.models import User
+from .models import Bookmark
+
+
+class UserForm(forms.ModelForm):
+ password = forms.CharField(widget=forms.PasswordInput())
+
+ class Meta:
+ model = User
+ fields = ('username', 'password',)
+
+class EditForm(forms.ModelForm):
+ class Meta:
+ model = Bookmark
+ fields = ('title', 'description', 'url',)
+
+class BookmarkForm(forms.ModelForm):
+ class Meta:
+ model = Bookmark
+ fields = ('url',)
\ No newline at end of file
diff --git a/urlybird/urly/migrations/0001_initial.py b/urlybird/urly/migrations/0001_initial.py
new file mode 100644
index 0000000..8e0441e
--- /dev/null
+++ b/urlybird/urly/migrations/0001_initial.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Bookmark',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)),
+ ('title', models.CharField(max_length=255)),
+ ('description', models.CharField(max_length=255)),
+ ('longurl', models.URLField()),
+ ('shorturl', models.CharField(max_length=8)),
+ ('time_created', models.DateTimeField(auto_now_add=True)),
+ ('user', models.ForeignKey(related_name='urly', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Click',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)),
+ ('timestamp', models.DateTimeField(auto_now_add=True)),
+ ('bookmark', models.ForeignKey(to='urly.Bookmark')),
+ ('user', models.ForeignKey(related_name='clicks', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/urlybird/urly/migrations/0002_auto_20150624_2212.py b/urlybird/urly/migrations/0002_auto_20150624_2212.py
new file mode 100644
index 0000000..015ce28
--- /dev/null
+++ b/urlybird/urly/migrations/0002_auto_20150624_2212.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('urly', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='click',
+ name='user',
+ field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/urlybird/urly/migrations/__init__.py b/urlybird/urly/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/urlybird/urly/models.py b/urlybird/urly/models.py
new file mode 100644
index 0000000..5096a22
--- /dev/null
+++ b/urlybird/urly/models.py
@@ -0,0 +1,68 @@
+from django.db import models
+from django.contrib.auth.models import User
+from faker import Factory
+import random, string
+
+# Create your models here.
+
+
+class Bookmark(models.Model):
+ user = models.ForeignKey(User, related_name="urly")
+ title = models.CharField(max_length=255)
+ description = models.CharField(max_length=255)
+ longurl = models.URLField()
+ shorturl = models.CharField(max_length=8)
+ time_created = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return self.shorturl
+
+
+class Click(models.Model):
+ user = models.ForeignKey(User)
+ bookmark = models.ForeignKey(Bookmark)
+ timestamp = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return str(self.timestamp)
+
+
+def create_user():
+ for i in range(1, 31):
+ user = User.objects.create_user(
+ "user{}".format(i),
+ "user{}@theironyard.com".format(i),
+ "user{}".format(i)
+ )
+ user.save()
+
+
+def create_bookmarks():
+ fake = Factory.create()
+ for user in User.objects.all():
+ for _ in range(random.randint(1, 30)):
+ title = fake.word()
+ description = fake.sentence(nb_words=4)
+ longurl = fake.url()
+ shorturl = generate_shorturl()
+ time_created = fake.date_time_this_month()
+ #count = random.randrange(1, 51)
+ bookmark = Bookmark(user=user, title=title, description=description, longurl=longurl, shorturl=shorturl,
+ time_created=time_created)
+ bookmark.save()
+
+
+def create_clicks():
+ fake = Factory.create()
+ for bookmark in Bookmark.objects.all():
+ for _ in range(random.randint(1, 50)):
+ user = random.choice(User.objects.all())
+ timestamp = fake.date_time_this_month()
+ click = Click(user=user, bookmark=bookmark, timestamp=timestamp)
+ click.save()
+
+
+def generate_shorturl():
+ rand_char = string.ascii_letters + string.digits
+ unique_code = ''.join(random.choice(rand_char) for i in range(8))
+ return unique_code
diff --git a/urlybird/urly/templates/registration/login.html b/urlybird/urly/templates/registration/login.html
new file mode 100644
index 0000000..3930ad7
--- /dev/null
+++ b/urlybird/urly/templates/registration/login.html
@@ -0,0 +1,35 @@
+{% extends "urly/base.html" %}
+
+{% block content %}
+
+{% if form.errors %}
+
Your username and password didn't match. Please try again.
+{% endif %}
+
+{% if next %}
+ {% if user.is_authenticated %}
+ Your account doesn't have access to this page. To proceed,
+ please login with an account that has access.
+ {% else %}
+ Please login to see this page.
+ {% endif %}
+{% endif %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/urlybird/urly/templates/registration/logout.html b/urlybird/urly/templates/registration/logout.html
new file mode 100644
index 0000000..6a04e68
--- /dev/null
+++ b/urlybird/urly/templates/registration/logout.html
@@ -0,0 +1,7 @@
+{% extends "urly/base.html" %}
+
+{% block content %}
+
+Logged out!
+
+{% endblock %}
\ No newline at end of file
diff --git a/urlybird/urly/templates/urly/base.html b/urlybird/urly/templates/urly/base.html
new file mode 100644
index 0000000..505949b
--- /dev/null
+++ b/urlybird/urly/templates/urly/base.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
You are logged in as {{ user }}.
+ {% if user.is_anonymous %}
+ Login
+ {% else %}
+ Logout
+ {% endif %}
+
+
+
+{% block content %}
+{% endblock %}
+
+
\ No newline at end of file
diff --git a/urlybird/urly/templates/urly/delete.html b/urlybird/urly/templates/urly/delete.html
new file mode 100644
index 0000000..0bb0947
--- /dev/null
+++ b/urlybird/urly/templates/urly/delete.html
@@ -0,0 +1,13 @@
+{% extends "urly/base.html" %}
+
+{% block content %}
+ Delete Bookmark
+ Title: {{ bookmark.title }}
+
+
+{% endblock %}
diff --git a/urlybird/urly/templates/urly/edit_bookmark.html b/urlybird/urly/templates/urly/edit_bookmark.html
new file mode 100644
index 0000000..0c89f28
--- /dev/null
+++ b/urlybird/urly/templates/urly/edit_bookmark.html
@@ -0,0 +1,13 @@
+{% extends "urly/base.html" %}
+
+{% block content %}
+ Edit Bookmark
+ Title: {{ bookmark.title }}
+
+{% endblock %}
\ No newline at end of file
diff --git a/urlybird/urly/templates/urly/index.html b/urlybird/urly/templates/urly/index.html
new file mode 100644
index 0000000..1250566
--- /dev/null
+++ b/urlybird/urly/templates/urly/index.html
@@ -0,0 +1,16 @@
+{% extends "urly/base.html" %}
+
+{% block content %}
+ Bookmarks
+
+ {% for bookmark in bookmarks %}
+
+
+
+ - Title: {{ bookmark.title }} | Description: {{ bookmark.description }} | ShortURL: {{ bookmark }}
+
+
+
+ {% endfor %}
+{% endblock %}
+