25 |
26 | {% endblock %}
--------------------------------------------------------------------------------
/web/dbpatterns/profiles/mixins.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from django.contrib.auth.decorators import login_required
4 | from django.http import HttpResponse
5 | from django.utils.decorators import method_decorator
6 |
7 |
8 | class LoginRequiredMixin(object):
9 | """
10 | Login required mixin.
11 | """
12 | @method_decorator(login_required)
13 | def dispatch(self, *args, **kwargs):
14 | return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)
15 |
16 |
17 | class JSONResponseMixin(object):
18 | """
19 | A mixin that can be used to render a JSON response.
20 | """
21 | response_class = HttpResponse
22 |
23 | def render_to_response(self, context, **response_kwargs):
24 | """
25 | Returns a JSON response, transforming 'context' to make the payload.
26 | """
27 | response_kwargs['content_type'] = 'application/json'
28 | return self.response_class(
29 | self.convert_context_to_json(context),
30 | **response_kwargs
31 | )
32 |
33 | def convert_context_to_json(self, context):
34 | """
35 | Convert the context dictionary into a JSON object
36 | """
37 | return json.dumps(context)
38 |
--------------------------------------------------------------------------------
/web/dbpatterns/dbpatterns/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for dbpatterns 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 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dbpatterns.settings")
19 |
20 | # This application object is used by any WSGI server configured to use this
21 | # file. This includes Django's development server, if the WSGI_APPLICATION
22 | # setting points here.
23 | from django.core.wsgi import get_wsgi_application
24 | application = get_wsgi_application()
25 |
26 | # Apply WSGI middleware here.
27 | # from helloworld.wsgi import HelloWorldApplication
28 | # application = HelloWorldApplication(application)
29 |
--------------------------------------------------------------------------------
/web/dbpatterns/profiles/resources.py:
--------------------------------------------------------------------------------
1 | from django.contrib.auth.models import User
2 | from django.db.models import Q
3 | from django.views.generic import ListView
4 |
5 | from gravatar.templatetags.gravatar import gravatar_for_user
6 |
7 | from profiles.mixins import JSONResponseMixin
8 |
9 |
10 | class UserResource(JSONResponseMixin, ListView):
11 | queryset = User.objects.all()
12 |
13 | def get_queryset(self):
14 | keyword = self.request.GET.get("term")
15 | excludes = self.request.GET.get("excludes", "").split(",")
16 |
17 | if self.request.user.is_authenticated():
18 | excludes.append(self.request.user.id)
19 |
20 | users = User.objects.filter(
21 | Q(username__icontains=keyword) |
22 | Q(email__icontains=keyword) |
23 | Q(first_name=keyword) | Q(last_name=keyword)
24 | ).exclude(id__in=filter(bool, excludes))
25 |
26 | return [dict(id=user.id,
27 | label=user.username,
28 | avatar=gravatar_for_user(user, size=40)) for user in users]
29 |
30 | def render_to_response(self, context, **response_kwargs):
31 | return super(UserResource, self).render_to_response(
32 | context.get("object_list"), **response_kwargs)
33 |
--------------------------------------------------------------------------------
/web/dbpatterns/documents/legacy_urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import url, patterns
2 | from django.views.generic import RedirectView
3 |
4 | urlpatterns = patterns(
5 | '',
6 | # Legacy URLs
7 |
8 | url(r'^my-documents/$',
9 | RedirectView.as_view(url="/documents/")),
10 | url(r'^search/$',
11 | RedirectView.as_view(url="/documents/search")),
12 | url(r'^new/$',
13 | RedirectView.as_view(url="/documents/new")),
14 | url(r'^show/(?P[-\w]+)/$',
15 | RedirectView.as_view(url="/documents/%(slug)s/")),
16 | url(r'^show/(?P[-\w]+)/forks$',
17 | RedirectView.as_view(
18 | url="/documents/%(slug)s/forks")),
19 | url(r'^show/(?P[-\w]+)/stars',
20 | RedirectView.as_view(
21 | url="/documents/%(slug)s/stars")),
22 | url(r'^edit/(?P[-\w]+)/', RedirectView.as_view(
23 | url="/documents/%(slug)s/edit")),
24 | url(r'^export/(?P[-\w]+)/(?P[-\w]+)',
25 | RedirectView.as_view(
26 | url="/documents/%(slug)s/export/%(exporter)s")),
27 | url(r'^fork/(?P[-\w]+)/$', RedirectView.as_view(
28 | url="/documents/%(slug)s/fork")),
29 | url(r'^star/(?P[-\w]+)/$', RedirectView.as_view(
30 | url="/documents/%(slug)s/star")),
31 | )
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | """
2 | dbpatterns dot com domain is not active anymore. soon the project
3 | might be relaunched again under a different domain.
4 | """
5 |
6 | The MIT License (MIT)
7 |
8 | Copyright (c) 2012 - 2021 Fatih Erikli
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
--------------------------------------------------------------------------------
/web/dbpatterns/dbpatterns/templates/documents/search.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 | {% load gravatar document_tags %}
3 |
4 | {% block body-id %}document-search{% endblock %}
5 |
6 | {% block content %}
7 |
8 |
9 | {% include "documents/search_form.html" %}
10 |
11 |
12 |
13 |
40 |
41 | Online: 2
42 |
43 | {% endblock %}
--------------------------------------------------------------------------------
/web/dbpatterns/dbpatterns/static/js/libs/backbone/backbone.shortcuts.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var Shortcuts;
3 |
4 | Shortcuts = function(options) {
5 | this.cid = _.uniqueId("backbone.shortcuts");
6 | this.initialize.apply(this, arguments);
7 | return this.delegateShortcuts();
8 | };
9 |
10 | _.extend(Shortcuts.prototype, Backbone.Events, {
11 | initialize: function() {},
12 | delegateShortcuts: function() {
13 | var callback, match, method, scope, shortcut, shortcutKey, _ref, _results;
14 | if (!this.shortcuts) return;
15 | _ref = this.shortcuts;
16 | _results = [];
17 | for (shortcut in _ref) {
18 | callback = _ref[shortcut];
19 | if (!_.isFunction(callback)) method = this[callback];
20 | if (!method) throw new Error("Method " + callback + " does not exist");
21 | match = shortcut.match(/^(\S+)\s*(.*)$/);
22 | shortcutKey = match[1];
23 | scope = match[2] === "" ? "all" : match[2];
24 | method = _.bind(method, this);
25 | _results.push(key(shortcutKey, scope, method));
26 | }
27 | return _results;
28 | }
29 | });
30 |
31 | key.filter = function () {
32 | // accept all elements
33 | return true;
34 | };
35 |
36 | Backbone.Shortcuts = Shortcuts;
37 |
38 | Backbone.Shortcuts.extend = Backbone.View.extend;
39 |
40 | }).call(this);
--------------------------------------------------------------------------------
/web/dbpatterns/dbpatterns/static/js/models/document.js:
--------------------------------------------------------------------------------
1 | dbpatterns.models.Document = Backbone.Model.extend({
2 |
3 | urlRoot: "/api/documents",
4 |
5 | initialize: function (model, options) {
6 | this.entities = new dbpatterns.collections.Entity;
7 | this.use_websocket = options.use_websocket;
8 | if (this.use_websocket) {
9 | this.socket = io.connect(options.socket_uri);
10 | this.socket.on("pull", this.pull.bind(this));
11 | this.on("load", this.bind_socket, this);
12 | } else {
13 | this.socket = {};
14 | _.extend(this.socket, Backbone.Events);
15 | }
16 | },
17 |
18 | parse: function (result) {
19 | if (!result) { return result; }
20 | this.entities.reset(result.entities);
21 | this.entities.bind("add remove persist", this.persist.bind(this));
22 | return result;
23 | },
24 |
25 | persist: function () {
26 | this.set({"entities": this.entities.toJSON()});
27 | },
28 |
29 | channel: function () {
30 | // specifies the channel of user
31 | return this.get("id")
32 | },
33 |
34 | push: function () {
35 | // pushes the changes to the channel of user
36 | this.socket.emit("push", this.toJSON());
37 | },
38 |
39 | pull: function (data) {
40 | // pulls the changes from the channel
41 | this.set(data);
42 | this.entities.reset(data.entities);
43 | },
44 |
45 | bind_socket: function () {
46 | this.on("change", this.push, this);
47 | }
48 |
49 | });
--------------------------------------------------------------------------------
/web/dbpatterns/dbpatterns/static/js/utilities.js:
--------------------------------------------------------------------------------
1 | !(function ($) {
2 |
3 | $(function () {
4 | // auto toggle manipulation
5 | $("a[data-toggle]").click(function () {
6 | var button = $(this),
7 | target = $(button.attr("href"));
8 | button.toggleClass("active");
9 | target.toggle().find("a").click(function (event) {
10 | target.hide();
11 | });
12 | return false;
13 | });
14 |
15 | // notifications
16 | $("#user-notifications-button").click(function () {
17 | var notifications = $("#user-notifications"),
18 | notifications_section = notifications.find("section"),
19 | notifications_bubble = $(this).find("span"),
20 | notifications_url = $(this).attr("href");
21 | notifications.toggle();
22 | notifications_bubble.remove();
23 | notifications_section.load(notifications_url);
24 | $.ajax({"type": "PUT", "url": notifications_url});
25 | return false;
26 | });
27 |
28 | // tab implementation
29 | $(".tabs nav a").click(function () {
30 | var tab_link = $(this),
31 | link_container = tab_link.parent(),
32 | focused_tab = $(tab_link.attr("href"));
33 | focused_tab.siblings("section").hide();
34 | focused_tab.show();
35 | link_container.siblings().removeClass("active");
36 | link_container.addClass("active");
37 | return false;
38 | });
39 |
40 | });
41 |
42 | })(window.jQuery);
--------------------------------------------------------------------------------
/tests/scenarios/following.feature:
--------------------------------------------------------------------------------
1 | Feature: Following users
2 |
3 | Background:
4 | Given following users exist
5 | | username | password |
6 | | edi | 123456 |
7 | | budu | 123456 |
8 |
9 | When I am logged in as user "edi"
10 |
11 | Scenario: users can follow the others
12 | When I go to the profile of "budu"
13 | And I click to follow button
14 | And I go to the profile of "budu" again
15 | Then the page should contains "edi following budu"
16 |
17 | Scenario: see followed profiles on the profile of follower
18 | When I go to the profile of "budu"
19 | And I click to follow button
20 | And I go to my profile
21 | Then the page should contains "edi following budu"
22 |
23 | Scenario: users can't follow himself
24 | When go to my profile
25 | The page should not contains "Follow"
26 | The page should not contains "Unfollow"
27 |
28 | Scenario: users can't follow already followed users
29 | When I go to the profile of "budu"
30 | And I click to follow button
31 | And I go to the profile of "budu" again
32 | And the page should not contains "Follow"
33 | And the page should contains "Unfollow"
34 |
35 | Scenario: users can't follow already followed profiles
36 | When I go to the profile of "budu"
37 | And I click to follow button
38 | When go to the profile of "budu" again
39 | The page should not contains "Follow"
40 | The page should contains "Unfollow"
41 |
42 | Scenario: user can unfollow followed profiles
43 | When I go to the profile of "budu"
44 | And I click to follow button
45 | And go to the profile of "budu" again
46 | And when click to unfollow button
47 | And I go to the profile of "budu" again
48 | Then the page should contains "Follow"
49 | And the page should not contains "Unfollow"
--------------------------------------------------------------------------------
/web/dbpatterns/dbpatterns/static/css/autocomplete.css:
--------------------------------------------------------------------------------
1 | .ui-widget-overlay {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | }
8 |
9 | * html .ui-autocomplete {
10 | width: 1px;
11 | }
12 |
13 | /* without this, the menu expands to 100% in IE6 */
14 | .ui-autocomplete {
15 | padding: 0;
16 | width: 200px;
17 | box-shadow: 0 0 13px #dcdcdc;
18 | position: absolute;
19 | top: 0;
20 | left: 0;
21 | cursor: default;
22 | }
23 |
24 | * html .ui-autocomplete {
25 | width: 1px;
26 | }
27 |
28 | /* without this, the menu expands to 100% in IE6 */
29 | .ui-menu {
30 | list-style: none;
31 | padding: 0;
32 | margin: 0;
33 | display: block;
34 | outline: none;
35 | }
36 |
37 | .ui-menu .ui-menu {
38 | margin-top: -3px;
39 | position: absolute;
40 | }
41 |
42 | .ui-menu .ui-menu-item {
43 | margin: 0;
44 | padding: 0;
45 | zoom: 1;
46 | width: 100%;
47 | }
48 |
49 | .ui-menu .ui-menu-divider {
50 | margin: 5px -2px 5px -2px;
51 | height: 0;
52 | font-size: 0;
53 | line-height: 0;
54 | border-width: 1px 0 0 0;
55 | }
56 |
57 | .ui-menu .ui-menu-item a {
58 | text-decoration: none;
59 | display: block;
60 | padding: 2px .4em;
61 | line-height: 1.5;
62 | zoom: 1;
63 | font-weight: normal;
64 | }
65 |
66 | .ui-menu .ui-menu-item a.ui-state-focus,
67 | .ui-menu .ui-menu-item a.ui-state-active {
68 | background-color: #2ca3f5;
69 | color: white;
70 | }
71 |
72 | .ui-menu .ui-state-disabled {
73 | font-weight: normal;
74 | margin: .4em 0 .2em;
75 | line-height: 1.5;
76 | }
77 |
78 | .ui-menu .ui-state-disabled a {
79 | cursor: default;
80 | }
81 |
82 | .ui-menu-item {
83 | list-style-type: none;
84 | background-color: white;
85 | padding: 3px;
86 | cursor: pointer;
87 | display: block;
88 | }
89 |
90 | .ui-helper-hidden-accessible {
91 | display: none;
92 | }
--------------------------------------------------------------------------------
/web/dbpatterns/dbpatterns/static/js/libs/hipo/hipo.infinityscroll.min.js:
--------------------------------------------------------------------------------
1 | $.namespace("hipo.InfinityScroll");hipo.InfinityScroll=$.Class.extend({loader_image:"",content_selector:"",pagination_selector:".pagination",next_link_selector:".pagination a.next",on_page_load:function(){},loader:null,FOOTER_POSITION_THRESHOLD:60,MOBILE_FOOTER_POSITION_THRESHOLD:600,init:function(a){$.extend(this,a);this.hide_pagination();this.check_scroll(this.load_page.bind(this));this.prepare_loader()},load_content:function(a){var b=$(this.content_selector,a).html();$(this.content_selector).append(b)},load_page:function(){var a=this.get_next_page();if(a){this.remove_pagination();this.show_loader();var b=function(c){this.load_content(c);this.hide_pagination();this.hide_loader();this.on_page_load()};$.get(a,b.bind(this),"html")}},get_next_page:function(){if($(this.next_link_selector).length){return $(this.next_link_selector).attr("href")}else{return false}},check_scroll:function(a){$(window).scroll(function(){if($(window).scrollTop()+$(window).height()>this.get_doc_height()-this.get_footer_threshold()){a()}}.bind(this))},hide_pagination:function(){$(this.pagination_selector).hide()},remove_pagination:function(){$(this.pagination_selector).remove()},prepare_loader:function(){this.loader=$("
").css({display:"none","text-align":"center",padding:"10px",clear:"both"}).append($("",{src:this.loader_image}));$(this.content_selector).after(this.loader)},show_loader:function(){this.loader.show()},hide_loader:function(){this.loader.hide()},get_footer_threshold:function(){return this.is_mobile_device()?this.MOBILE_FOOTER_POSITION_THRESHOLD:this.FOOTER_POSITION_THRESHOLD},get_doc_height:function(){var a=document;return Math.max(Math.max(a.body.scrollHeight,a.documentElement.scrollHeight),Math.max(a.body.offsetHeight,a.documentElement.offsetHeight),Math.max(a.body.clientHeight,a.documentElement.clientHeight))},is_mobile_device:function(){return navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad|android)/)}});
--------------------------------------------------------------------------------
/web/dbpatterns/dbpatterns/static/css/prettify.css:
--------------------------------------------------------------------------------
1 | pre {display: block; clear: both; margin: 0; background-color: #333; padding: 10px; font-size: 13px }
2 | pre code {
3 | font-family: 'Monaco', 'Consolas', 'Bitstream Vera Sans Mono', 'Courier-Bold', 'Courier New', monospace;
4 | -webkit-font-smoothing: subpixel-antialiased;
5 |
6 | }
7 | pre .nocode { background-color: none; color: #000 }
8 | pre .str { color: #ffa0a0 } /* string - pink */
9 | pre .kwd { color: #f0e68c; font-weight: normal }
10 | pre .com { color: #87ceeb } /* comment - skyblue */
11 | pre .typ { color: #98fb98 } /* type - lightgreen */
12 | pre .lit { color: #cd5c5c } /* literal - darkred */
13 | pre .pun { color: #fff } /* punctuation */
14 | pre .pln { color: #fff } /* plaintext */
15 | pre .tag { color: #f0e68c; font-weight: normal } /* html/xml tag - lightyellow */
16 | pre .atn { color: #bdb76b; font-weight: normal } /* attribute name - khaki */
17 | pre .atv { color: #ffa0a0 } /* attribute value - pink */
18 | pre .dec { color: #98fb98 } /* decimal - lightgreen */
19 |
20 | /* Specify class=linenums on a pre to get line numbering */
21 | ol.linenums { margin-top: 0; margin-bottom: 0; color: #AEAEAE } /* IE indents via margin-left */
22 | li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8 { list-style-type: none }
23 | /* Alternate shading for lines */
24 | li.L1,li.L3,li.L5,li.L7,li.L9 { }
25 |
26 | @media print {
27 | pre { background-color: none }
28 | pre .str, code .str { color: #060 }
29 | pre .kwd, code .kwd { color: #006; font-weight: normal }
30 | pre .com, code .com { color: #600; font-style: italic }
31 | pre .typ, code .typ { color: #404; font-weight: normal }
32 | pre .lit, code .lit { color: #044 }
33 | pre .pun, code .pun { color: #440 }
34 | pre .pln, code .pln { color: #000 }
35 | pre .tag, code .tag { color: #006; font-weight: normal }
36 | pre .atn, code .atn { color: #404 }
37 | pre .atv, code .atv { color: #060 }
38 | }
--------------------------------------------------------------------------------
/tests/scenarios/private-documents.feature:
--------------------------------------------------------------------------------
1 | Feature: Private Documents
2 | In order to create documents which can be seen by just me,
3 | As an authenticated user,
4 | I want to create a private document.
5 |
6 | Background:
7 | Given following users exist
8 | | username | password |
9 | | edi | 123456 |
10 | | budu | 123456 |
11 |
12 | When I am logged in as user "edi"
13 |
14 | Scenario: Create private patterns
15 | When go to the create pattern page
16 | And I type the "title" as "Friendships"
17 | And I choose the "is_public" option as "False"
18 | When I click to save button
19 | Then the redirected page should contains "Make Public"
20 |
21 | Scenario: The others can not see the private documents
22 | When go to the create pattern page
23 | And I type the "title" as "Hey, it's just me"
24 | And I choose the "is_public" option as "False"
25 | When I click to save button
26 | And I am logged out
27 | And I am logged in as user "budu"
28 | And I go to the created pattern
29 | Then the page should return with 404 status code.
30 |
31 | Scenario: The creator of a private pattern can make it public
32 | When go to the create pattern page
33 | And I type the "title" as "Hey, I make it public after the organizing"
34 | And I choose the "is_public" option as "False"
35 | When I click to save button
36 | And click to the make public button
37 | And I am logged out
38 | And I am logged in as user "budu"
39 | And I go to the created pattern
40 | Then the page should contains "Hey, I make it public after the organizing"
41 |
42 | Scenario: Private patterns can't be visible on the newsfeed.
43 | When go to the create pattern page
44 | And I type the "title" as "Secret work"
45 | And I choose the "is_public" option as "False"
46 | When I click to save button
47 | And I go to the newsfeed
48 | The page should not contains "Secret work"
49 |
--------------------------------------------------------------------------------
/web/dbpatterns/dbpatterns/templates/documents/show.html:
--------------------------------------------------------------------------------
1 | {% extends "documents/app.html" %}
2 | {% load gravatar document_tags %}
3 |
4 | {% block body-id %}document-show-view{% endblock %}
5 |
6 | {% block tools %}
7 |