├── .gitignore
├── README.rst
└── yuk
├── __init__.py
├── admin.py
├── fabfile.py
├── forms.py
├── localsettings.py
├── manage.py
├── migrate.py
├── models.py
├── processors.py
├── requirements.txt
├── rss_module.py
├── scripts.py
├── search_indexes.py
├── settings.py
├── srchupdate.py
├── static
├── bg-stripe.gif
├── black-stripe.gif
├── css
│ └── blueprint
│ │ ├── icons
│ │ ├── cross.png
│ │ ├── key.png
│ │ └── tick.png
│ │ ├── ie.css
│ │ ├── print.css
│ │ ├── readme.txt
│ │ └── screen.css
└── green-stripe.gif
├── templates
├── 401.html
├── 404.html
├── 500.html
├── base.html
├── bm_login.html
├── bookmark_import.html
├── bookmarklet_add.html
├── del_url.html
├── edit_url.html
├── export.html
├── index.html
├── landing.html
├── new_item.html
├── registration
│ ├── activate.html
│ ├── activation_email.txt
│ ├── activation_email_subject.txt
│ ├── login.html
│ ├── logout.html
│ ├── registration_complete.html
│ └── registration_form.html
├── rss_import.html
├── search
│ ├── indexes
│ │ └── yuk
│ │ │ ├── item_text.txt
│ │ │ └── url_text.txt
│ ├── search.html
│ └── search_form.html
├── stored.html
├── tag.html
├── unknown.html
└── user_profile.html
├── templatetags
├── __init__.py
└── timesince.py
├── tests.py
├── urls.py
└── views.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.bak
2 | *.scss
3 | *.scss~
4 | y_screen.css
5 | hosts.py
6 | *.pyc
7 | *.py~
8 | *.html~
9 | *.swp
10 | *.*~
11 | \#*.*
12 | .\#*.*
13 | .gitignore
14 | djsecrets.py
15 | links.db
16 | twitter_creds.py
17 | /a/mattdeboard.net/yukproj/oauth_provider/
18 | fixtures/
19 | yuk/oauth.py
20 | yukimport/
21 | yuk/migrations/
22 | yuk/whoosh/*
23 | /a/mattdeboard.net/yukproj/yuk/migrations/
24 | TODO.rst
25 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ====
2 | Yuk
3 | ====
4 |
5 | **Yuk** is a web bookmark service ( think `Pinboard`_ ) built on top of Django,
6 | using Postgres. It remains a self-learning exercise as I continue to refine and
7 | expand its features and functionality.
8 |
9 | Currently, it supports storing three types of information:
10 | 1. Bookmarks
11 | 2. Notes
12 | 3. Quotes
13 |
14 | **Yuk** also supports importing and exporting bookmarks in the `del.icio.us`_
15 | HTL format. You can also import bookmarks via RSS feeds. I also have a book-
16 | marklet implemented in the base.html template.
17 |
18 | Fundamentally this is a platform for me to try new things in web development,
19 | but just by coincidence it also happens to be a minimal, but functional,
20 | web bookmarking service that I use every day. You can see my profile page
21 | `here `_, or register `here `_.
22 |
23 | .. _Pinboard.in: http://pinboard.in
24 | .. _del.icio.us: http://del.icio.us
25 |
26 |
--------------------------------------------------------------------------------
/yuk/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdeboard/yukproj/d1ee5b2ad957e8a01a11ba2ea3d325796412812f/yuk/__init__.py
--------------------------------------------------------------------------------
/yuk/admin.py:
--------------------------------------------------------------------------------
1 | from yuk.models import Item
2 | from django.contrib import admin
3 |
4 |
5 | class ItemAdmin(admin.ModelAdmin):
6 | list_display = ('date_created', 'user', 'displays', 'item_type')
7 |
8 | admin.site.register(Item, ItemAdmin)
9 |
--------------------------------------------------------------------------------
/yuk/fabfile.py:
--------------------------------------------------------------------------------
1 | from calendar import timegm
2 | from time import gmtime
3 |
4 | from fabric.api import *
5 | from hosts import hosts, secret
6 |
7 | env.password = secret
8 | env.hosts = hosts
9 | domain_dir = "/a/mattdeboard.net/"
10 | appdir = domain_dir + "src/yukproj/"
11 | whoosh_dir = appdir + "yuk/whoosh/"
12 | # directory where git puts the css files on git pull
13 | css_dir = appdir + "yuk/static/css/blueprint/"
14 | # where nginx looks for static files
15 | static_file_dir = domain_dir + "root/yukmarks/css/blueprint/"
16 | pg_dump_dir = domain_dir + "pg_dumps/"
17 |
18 |
19 | def run_all():
20 | git_pull()
21 | dump_data()
22 | pg_dump()
23 |
24 | def git_pull():
25 | run("cd %s; . bin/activate; cd %s; git pull; ./manage.py schemamigration"
26 | " --auto yuk; ./manage.py migrate yuk;cp %s* %s;sudo /etc/init.d/apache2"
27 | " force-reload" % (domain_dir, appdir, css_dir, static_file_dir))
28 |
29 | def pg_dump():
30 | timestamp = timegm(gmtime())
31 | run("cd %s; . bin/activate; cd %s; pg_dump -f %spg_dump_%s.sql pg_links" %
32 | (domain_dir, appdir, pg_dump_dir, timestamp))
33 |
34 | def dump_data():
35 | timestamp = timegm(gmtime())
36 | run("cd %s; . bin/activate; cd %s; ./manage.py dumpdata --format=json yuk"
37 | " >> /a/mattdeboard.net/yuk_data_dumps/dump_%s.json" %
38 | (domain_dir, appdir, timestamp))
39 |
40 | def update_search():
41 | run("sudo -u www-data /a/mattdeboard.net/bin/python %smanage.py update_inde"
42 | "x; sudo /etc/init.d/apache2 force-reload" % appdir)
43 |
44 | def rebuild_search():
45 | run("cd %s; . bin/activate; cd %s; sudo chown matt:matt %s; sudo chown matt"
46 | ":matt %s*; ./manage.py rebuild_index; sudo chown www-data:www-data %s;"
47 | " sudo chown www-data:www-data %s*; sudo /etc/init.d/apache2 force-relo"
48 | "ad" % (domain_dir, appdir, whoosh_dir,
49 | whoosh_dir, whoosh_dir, whoosh_dir))
50 |
--------------------------------------------------------------------------------
/yuk/forms.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from urlparse import urlparse, urlunparse
3 |
4 | from django.forms import ModelForm
5 | from django import forms
6 | from taggit.forms import TagField
7 | from haystack.forms import SearchForm
8 |
9 | from yuk.models import Item
10 |
11 |
12 | class MySearchForm(SearchForm):
13 | q = forms.CharField(label="Search:",
14 | widget=forms.TextInput(attrs={'size': '35',
15 | 'rows': '2'}))
16 |
17 | def __init__(self, load_all=True, *args, **kwargs):
18 | super(MySearchForm, self).__init__(*args, **kwargs)
19 |
20 |
21 | class MyUrlField(forms.URLField):
22 |
23 | def to_python(self, value):
24 | '''Lowercase the URL input for validation.'''
25 | if '://' in value:
26 | return self.lowercase_domain(value)
27 | else:
28 | return self.lowercase_domain('http://%s' % value)
29 |
30 | def lowercase_domain(self, url):
31 | parsed = urlparse(url)
32 | retval = urlunparse((parsed.scheme,
33 | parsed.netloc.lower(),
34 | parsed.path,
35 | parsed.params,
36 | parsed.query,
37 | parsed.fragment))
38 | if url.endswith('?') and not retval.endswith('?'):
39 | retval += '?'
40 | return retval
41 |
42 |
43 | class UrlForm(ModelForm):
44 |
45 | url = MyUrlField(label='URL:', widget=forms.TextInput(attrs={'size':'35'}),
46 | required=True)
47 | displays = forms.CharField(label = 'Name:', required=False,
48 | widget=forms.TextInput(attrs={'size':'35'}))
49 | description = forms.CharField(label='Description (max 500 chars):',
50 | widget = forms.Textarea(attrs={'cols': '35',
51 | 'rows':'10'}),
52 | required = False)
53 | privacy_mode = forms.BooleanField(label="Private?", required=False,
54 | widget=forms.CheckboxInput)
55 |
56 | class Meta:
57 | model = Item
58 | exclude = ('user', 'date_created', 'last_updated', 'item_type')
59 | fields = ('url', 'displays', 'description', 'tags', 'privacy_mode')
60 |
61 | def __init__(self, data=None, user=None, *args, **kwargs):
62 | super(UrlForm, self).__init__(data, *args, **kwargs)
63 | self.user = user
64 |
65 | def clean_url(self):
66 | url = self.cleaned_data['url']
67 | if self.user.item_set.filter(url=url).count():
68 | raise forms.ValidationError("You already saved this bookmark!")
69 | else:
70 | return url
71 |
72 | def clean_tags(self):
73 | tags = self.cleaned_data['tags']
74 | for tag in tags:
75 | tags[tags.index(tag)] = tag.lower()
76 | return tags
77 |
78 |
79 | class UrlEditForm(ModelForm):
80 | url = MyUrlField(label='URL:', widget=forms.TextInput(attrs={'size':'35'}),
81 | required=True)
82 | displays = forms.CharField(label = 'Name:', required=False,
83 | widget=forms.TextInput(attrs={'size':'35'}))
84 | description = forms.CharField(label='Description (max 500 chars):',
85 | widget = forms.Textarea(attrs={'cols': '35',
86 | 'rows':'10'}),
87 | required = False)
88 | privacy_mode = forms.BooleanField(label="Private?", required=False,
89 | widget=forms.CheckboxInput)
90 |
91 | class Meta:
92 | model = Item
93 | exclude = ('user', 'date_created', 'last_updated', 'item_type')
94 | fields = ('url', 'displays', 'description', 'tags', 'privacy_mode')
95 |
96 | def __init__(self, data=None, user=None, *args, **kwargs):
97 | super(UrlEditForm, self).__init__(data, *args, **kwargs)
98 | self.user = user
99 |
100 | def clean_tags(self):
101 | tags = self.cleaned_data['tags']
102 | for tag in tags:
103 | tags[tags.index(tag)] = tag.lower()
104 | return tags
105 |
106 | class RssImportForm(ModelForm):
107 | url = MyUrlField(label="URL of RSS feed:", required=True)
108 |
109 | class Meta:
110 | model = Item
111 | exclude = ('user', 'date_created', 'last_updated', 'description',
112 | 'displays', 'privacy_mode', 'tags')
113 |
114 | def __init__(self, data=None, user=None, *args, **kwargs):
115 | super(RssImportForm, self).__init__(data, *args, **kwargs)
116 | self.user = user
117 |
118 | def clean_url(self):
119 | url = self.cleaned_data['url']
120 | if self.user.rssfeed_set.filter(url=url).count():
121 | raise forms.ValidationError("This URL already exists for %s" %
122 | self.user)
123 | else:
124 | return url
125 |
126 | class BookmarkUploadForm(forms.Form):
127 |
128 | filename = forms.CharField(max_length=50)
129 | import_file = forms.FileField()
130 |
131 |
132 | class NoteForm(ModelForm):
133 |
134 | displays = forms.CharField(label='Title:', required=True,
135 | widget=forms.TextInput(attrs={'size':'35'}))
136 | description = forms.CharField(label='Notes:',
137 | widget=forms.Textarea(attrs={'cols':'35',
138 | 'rows':'15'}),
139 | required=False)
140 | privacy_mode = forms.BooleanField(label="Private?", required=False,
141 | widget=forms.CheckboxInput)
142 |
143 | class Meta:
144 | model = Item
145 | exclude = ('user', 'date_created','url', 'last_updated')
146 | fields = ('displays', 'description', 'tags', 'privacy_mode')
147 |
148 | def __init__(self, data=None, user=None, *args, **kwargs):
149 | super(NoteForm, self).__init__(data, *args, **kwargs)
150 | self.user = user
151 |
152 | def clean_tags(self):
153 | tags = self.cleaned_data['tags']
154 | for tag in tags:
155 | tags[tags.index(tag)] = tag.lower()
156 | return tags
157 |
158 |
159 | class QuoteForm(ModelForm):
160 |
161 | description = forms.CharField(label='Quote:',
162 | widget=forms.Textarea(attrs={'cols':'35',
163 | 'rows':'15'}),
164 | required=True)
165 | displays = forms.CharField(label='Who said it?', required=True,
166 | widget = forms.TextInput(attrs={'size':'35'}))
167 | privacy_mode = forms.BooleanField(label="Private?", required=False,
168 | widget=forms.CheckboxInput)
169 | tags = TagField(required=False)
170 | class Meta:
171 | model = Item
172 | exclude = ('user', 'date_created', 'url', 'last_updated')
173 | fields = ('description', 'displays', 'tags', 'privacy_mode')
174 |
175 | def __init__(self, data=None, user=None, *args, **kwargs):
176 | super(QuoteForm, self).__init__(data, *args, **kwargs)
177 | self.user = user
178 |
179 | def clean_tags(self):
180 | tags = self.cleaned_data['tags']
181 | print >> sys.stderr, tags
182 | for tag in tags:
183 | tags[tags.index(tag)] = tag.lower()
184 | return tags
185 |
--------------------------------------------------------------------------------
/yuk/localsettings.py:
--------------------------------------------------------------------------------
1 | DEBUG = True
2 |
3 | TEMPLATE_DIS = (
4 | '/a/yukproj/yuk/templates',
5 | )
6 |
7 | HAYSTACK_WHOOSH_PATH = '/a/mattdeboard.net/src/yukproj/yuk/whoosh'
8 |
9 | STATIC_DOC_ROOT = "/a/mattdeboard.net/src/yukproj/yuk/static"
10 |
11 | SITE_URL = "http://yukmarks.com"
12 |
13 | MEDIA_URL = "/site_media/"
14 |
15 | BLUEPRINT_PATH = MEDIA_URL + "css/blueprint/"
16 |
17 |
--------------------------------------------------------------------------------
/yuk/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | try:
4 | import settings # Assumed to be in the same directory.
5 | except ImportError:
6 | import sys
7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
8 | sys.exit(1)
9 |
10 | if __name__ == "__main__":
11 | execute_manager(settings)
12 |
--------------------------------------------------------------------------------
/yuk/migrate.py:
--------------------------------------------------------------------------------
1 | from yuk.models import *
2 |
3 | def migrate_urls():
4 | for url in Url.objects.all():
5 | b = Item(user=url.user, date_created=url.date_created,
6 | last_updated=url.last_updated,
7 | privacy_mode=url.privacy_mode, url=url.url,
8 | displays=url.url_name, url_desc=url.url_desc,
9 | item_type="bookmark")
10 | b.save()
11 | for tag in ur.tags.all():
12 | b.tags.add(tag)
13 | b.save()
14 |
15 | return "Done - Bookmarks"
16 |
17 | def migrate_notes():
18 | for note in Note.objects.all():
19 | n = Item(user=note.user, date_created=note.date_created,
20 | last_updated=note.last_updated, privacy_mode=note.privacy_mode,
21 | url=note.url, displays=note.title, description=note.notes,
22 | item_type="note")
23 | n.save()
24 | for tag in note.tags.all():
25 | n.tags.add(tag)
26 | n.save()
27 |
28 | return "Done - Notes"
29 |
30 | def migrate_quotes():
31 | for quote in Quote.objects.all():
32 | n = Item(user=quote.user, date_created=quote.date_created,
33 | last_updated=quote.last_updated,
34 | privacy_mode=quote.privacy_mode, url=quote.url,
35 | displays=quote.title, description=quote.notes,
36 | item_type="quote")
37 | n.save()
38 | for tag in note.tags.all():
39 | n.tags.add(tag)
40 | n.save()
41 |
42 | return "Done - Quotes"
43 |
--------------------------------------------------------------------------------
/yuk/models.py:
--------------------------------------------------------------------------------
1 | import urllib
2 | import datetime
3 |
4 | from django.db import models
5 | from django.contrib.auth.models import User
6 | from django.utils.encoding import smart_str
7 | from taggit.managers import TaggableManager
8 |
9 |
10 | class Item(models.Model):
11 |
12 | user = models.ForeignKey(User)
13 | url = models.URLField(verify_exists=False, blank=True, max_length=500)
14 | date_created = models.DateTimeField(default=datetime.datetime.now())
15 | last_updated = models.DateTimeField(default=datetime.datetime.now(),
16 | auto_now=True)
17 | tags = TaggableManager()
18 | privacy_mode = models.BooleanField()
19 | # 'displays' attribute is "source" for QuoteItem, "title" for
20 | # NoteItem and Bookmark.
21 | displays = models.CharField(max_length=500, blank=True)
22 | # URL description for bookmarks, text of the quote for QuoteItems,
23 | # text of the note for Notes.
24 | description = models.TextField()
25 | # bookmark, note or quote
26 | item_type = models.CharField(max_length=200)
27 |
28 | class Meta:
29 | ordering = ['-date_created']
30 |
31 |
32 | # monkey-patch to get my profile page URLs how I want them.
33 | def func_to_method(func, cls, name=None):
34 | import new
35 | method = new.instancemethod(func, None, cls)
36 | if not name:
37 | name = func.__name__
38 | setattr(cls, name, method)
39 |
40 | def get_absolute_url(self):
41 | return '/u:%s' % urllib.quote(smart_str(self.username))
42 |
43 | func_to_method(get_absolute_url, User)
44 |
45 |
--------------------------------------------------------------------------------
/yuk/processors.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from yuk.forms import MySearchForm
4 |
5 | from yuk.settings import SITE_URL, MEDIA_URL, BLUEPRINT_PATH
6 | from yuk.localsettings import SITE_URL as LOCAL_SITE_URL
7 | from yuk.localsettings import MEDIA_URL as LOCAL_MEDIA_URL
8 | from yuk.localsettings import BLUEPRINT_PATH as LOCAL_BLUEPRINT_PATH
9 |
10 | def site_url_processor(request):
11 | if os.environ['DJANGO_SETTINGS_MODULE'] == 'yuk.localsettings':
12 | return {'site_url': LOCAL_SITE_URL,
13 | 'media_url': LOCAL_MEDIA_URL,
14 | 'blueprint_path': LOCAL_BLUEPRINT_PATH}
15 |
16 | return {'site_url': SITE_URL,
17 | 'media_url': MEDIA_URL,
18 | 'blueprint_path': BLUEPRINT_PATH}
19 |
20 | def text_area_processor(request):
21 | '''Processor to make handling text area widgets in templates a little
22 | easier.'''
23 | return {'text_areas':['url_desc', 'notes', 'quote']}
24 |
25 | def search_processor(request):
26 | return {'search_form': MySearchForm()}
27 |
--------------------------------------------------------------------------------
/yuk/requirements.txt:
--------------------------------------------------------------------------------
1 | Django==1.3.1
2 | South==0.7.3
3 | amqplib==1.0.2
4 | anyjson==0.3.1
5 | celery==2.4.0
6 | django-extensions==0.7.1
7 | django-haystack==1.2.5
8 | django-registration
9 | django-taggit==0.9.3
10 | django-taggit-templatetags==0.4.6dev
11 | django-templatetag-sugar==0.1
12 | git+git://github.com/ipython/ipython.git
13 | kombu==1.4.3
14 | python-dateutil==1.5
15 | wsgiref==0.1.2
16 |
--------------------------------------------------------------------------------
/yuk/rss_module.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import unittest
3 | from calendar import timegm
4 | from time import mktime, gmtime
5 | from datetime import datetime
6 | from operator import itemgetter
7 |
8 | import feedparser
9 |
10 |
11 |
12 | class TestSequenceFunctions(unittest.TestCase):
13 |
14 | def setUp(self):
15 | self.username = 'testuser'
16 | self.feedurl_invalid = 'http://www.cnn.com'
17 | self.feedurl_valid = 'http://rss.cnn.com/rss/cnn_topstories'
18 | self.past = timegm(gmtime()) - 15000
19 | self.future = timegm(gmtime()) + 15000
20 |
21 | def test_bad_url_past(self):
22 | # Make sure an empty dict gets returned for an invalid URL
23 | test_feed = rssdownload(self.username, self.feedurl_invalid, self.past)
24 | self.assertTrue(len(test_feed['messages'])==0)
25 |
26 | def test_bad_url_future(self):
27 | # Make sure an empty dict gets returned for an invalid URL
28 | test_feed = rssdownload(self.username, self.feedurl_invalid,
29 | self.future)
30 | self.assertTrue(len(test_feed['messages'])==0)
31 |
32 | def test_good_url_past(self):
33 | # Make sure an empty dict gets returned for a valid URL
34 | test_feed = rssdownload(self.username, self.feedurl_valid, self.past)
35 | self.assertTrue(len(test_feed['messages'])>0,
36 | 'Probably no new links found...')
37 |
38 | def test_good_url_future(self):
39 | # Make sure an empty dict gets returned for a valid URL
40 | test_feed = rssdownload(self.username, self.feedurl_valid, self.future)
41 | self.assertTrue(len(test_feed['messages'])==0)
42 |
43 | def rssdownload(username, feedurl, last_reference=0, mode=0):
44 | ''' --> rssdownload(username, feedurl, last_reference=0)
45 |
46 | 'username' is used exclusively for logging purposes at this time.
47 | 'feedurl' must be a valid RSS feed. Validation is performed by
48 | checking the parsed data from the URL for the tag, which
49 | is RSS 2.0 standard. If feedurl is not a valid RSS URL by that
50 | standard, an empty dictionary object is returned, and an error is
51 | logged.
52 |
53 | 'last_reference' is a datetime.datetime() of the last time this
54 | URL was polled. This time is determined by getting the time the
55 | most recent article was last updated. Only links added or updated
56 | after last_reference are returned to the user. If there are no
57 | new links, an error is logged and an empty dictionary object is
58 | returned.
59 |
60 | mode 0 = default. mode 1 = will search the feed entries for some
61 | fields commonly used to contain body text. If these fields are
62 | found, they will be parsed for links, and be returned from this
63 | function as a separate dictionary object.'''
64 |
65 | messages = []
66 | feed = feedparser.parse(feedurl)
67 |
68 | logger = logging.getLogger('proxy.rss')
69 | logger.debug("User %s's update URL is %s" % (username, feedurl))
70 |
71 | if 'title' not in feed.feed:
72 | logger.error('User %s supplied a URL that does not seem to be a valid R'
73 | 'SS feed (%s)' %
74 | (username, feedurl))
75 | return {'messages': messages, 'last_reference': last_reference,
76 | 'protected': False}
77 |
78 | for item in feed.entries:
79 | #feedparser returns timestamp as a time.struct_time object (named
80 | #tuple) .. the next line converts to datetime.datetime()
81 | tstamp = datetime.fromtimestamp(mktime((item.updated_parsed)))
82 | message = {'url': item.link,
83 | 'timestamp': tstamp,
84 | 'url_name': item.title}
85 |
86 | messages.append(message)
87 |
88 | messages.sort(key = itemgetter('timestamp'))
89 | last_ref = messages[-1]['timestamp']
90 |
91 | return {'messages': messages,
92 | 'last_reference': last_ref,
93 | 'protected': False}
94 |
--------------------------------------------------------------------------------
/yuk/scripts.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from yuk.models import Item
3 | from BeautifulSoup import BeautifulSoup
4 |
5 | def import_text_file(request):
6 | '''Allows import of bookmark text file using the pinboard/del.icio.us
7 | model of HTML exports. Each line containing a bookmark is marked by
8 | an unclosed tag, and a description set off by tag.
9 |
10 | (Example of this:
11 |
12 | Foo
13 | Bar
14 | This is a description of a webpage about Bar. '''
15 |
16 | soup = BeautifulSoup(request.FILES['import_file'])
17 | alltags = soup.findAll('a')
18 |
19 | for item in alltags:
20 | # Create a new Item for each tag in the uploaded file.
21 | ts = float(item.get('add_date'))
22 | u = Item(user=request.user, url=item.get('href'),
23 | displays=item.text, privacy_mode=bool(int(item.get('private'))),
24 | date_created=datetime.fromtimestamp(ts))
25 | tags = item.get('tags').split(',')
26 | u.save()
27 | for tag in tags:
28 | u.tags.add(tag)
29 | u.save()
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/yuk/search_indexes.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | from haystack import indexes
3 | from yuk.models import Item
4 |
5 | class ItemIndex(indexes.RealTimeSearchIndex, indexes.Indexable):
6 | text = indexes.CharField(document=True, use_template=True)
7 | author = indexes.CharField(model_attr='user')
8 | private = indexes.CharField(model_attr='privacy_mode')
9 | url_id = indexes.CharField(model_attr='id')
10 | url = indexes.CharField(model_attr='url')
11 | url_name = indexes.CharField(model_attr='displays')
12 | url_desc = indexes.CharField(model_attr='description')
13 | tags = indexes.MultiValueField()
14 |
15 | def get_model(self):
16 | return Item
17 |
18 | def index_queryset(self):
19 | """Used when the entire index for model is updated."""
20 | return Item.objects.filter(date_created__lte=datetime.datetime.now())
21 |
22 | def prepare_tags(self, obj):
23 | return [tag.name for tag in obj.tags.all()]
24 |
25 |
--------------------------------------------------------------------------------
/yuk/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import djcelery
4 |
5 | from djsecrets import *
6 |
7 | djcelery.setup_loader()
8 |
9 | DEBUG = False
10 | TEMPLATE_DEBUG = DEBUG
11 |
12 | # ADMINS = (
13 | # ('Matt DeBoard', 'matt.deboard@gmail.com'),
14 | # )
15 |
16 | # MANAGERS = ADMINS
17 |
18 | DATABASES = {
19 | 'default': {
20 | 'ENGINE': 'django.db.backends.postgresql_psycopg2',
21 | 'NAME': 'pg_links',
22 | 'USER': '',
23 | 'PASSWORD': '',
24 | 'HOST': '',
25 | 'PORT': '',
26 | }
27 | }
28 |
29 | TIME_ZONE = 'America/Chicago'
30 |
31 | DEFAULT_FROM_EMAIL = 'admin@yukmarks.com'
32 |
33 | LANGUAGE_CODE = 'en-us'
34 |
35 | SITE_ID = 1
36 |
37 | USE_I18N = True
38 |
39 | USE_L10N = True
40 |
41 | MEDIA_ROOT = ''
42 |
43 | MEDIA_URL = "http://media.yukmarks.com/"
44 |
45 | ADMIN_MEDIA_PREFIX = '/media/'
46 |
47 |
48 | SECRET_KEY = secret
49 |
50 | TEMPLATE_LOADERS = (
51 | 'django.template.loaders.filesystem.Loader',
52 | 'django.template.loaders.app_directories.Loader',
53 | )
54 |
55 | MIDDLEWARE_CLASSES = (
56 | 'django.middleware.cache.UpdateCacheMiddleware',
57 | 'django.middleware.common.CommonMiddleware',
58 | 'django.contrib.sessions.middleware.SessionMiddleware',
59 | 'django.middleware.csrf.CsrfViewMiddleware',
60 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
61 | 'django.contrib.messages.middleware.MessageMiddleware',
62 | 'django.middleware.cache.FetchFromCacheMiddleware',
63 | )
64 |
65 | ROOT_URLCONF = 'yuk.urls'
66 |
67 | TEMPLATE_DIRS = (
68 | '/a/mattdeboard.net/src/yukproj/yuk/templates',
69 | )
70 |
71 | TEMPLATE_CONTEXT_PROCESSORS = (
72 | "django.contrib.auth.context_processors.auth",
73 | "django.core.context_processors.debug",
74 | "django.core.context_processors.i18n",
75 | "django.core.context_processors.media",
76 | "django.contrib.messages.context_processors.messages",
77 | "yuk.processors.site_url_processor",
78 | "yuk.processors.text_area_processor",
79 | "yuk.processors.search_processor",
80 | )
81 |
82 | INSTALLED_APPS = (
83 | 'django.contrib.auth',
84 | 'django.contrib.contenttypes',
85 | 'django.contrib.sessions',
86 | 'django.contrib.sites',
87 | 'django.contrib.messages',
88 | 'django.contrib.admin',
89 | 'django.contrib.admindocs',
90 | 'taggit',
91 | 'taggit_templatetags',
92 | 'registration',
93 | 'south',
94 | 'haystack',
95 | 'django_extensions',
96 | 'yuk',
97 | )
98 |
99 | ACCOUNT_ACTIVATION_DAYS = 7
100 |
101 | BLUEPRINT_PATH = MEDIA_URL + "css/blueprint/"
102 |
103 | HAYSTACK_LIMIT_TO_REGISTERED_MODELS = False
104 | HAYSTACK_CONNECTIONS = {
105 | 'default': {
106 | 'ENGINE': 'haystack.backends.solr_backend.SolrEngine',
107 | 'URL': 'http://184.106.93.223:8983/solr'
108 | }
109 | }
110 |
111 | SITE_URL = "http://yukmarks.com"
112 |
--------------------------------------------------------------------------------
/yuk/srchupdate.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 | import logging
4 |
5 | domain_dir = "/a/mattdeboard.net/"
6 | appdir = domain_dir + "src/yukproj/"
7 | whoosh_dir = appdir + "yuk/whoosh/"
8 |
9 | def update():
10 | logging.basicConfig(filename='/a/mattdeboard.net/src/index.log',
11 | level=logging.INFO,
12 | format='%(asctime)s %(levelname)s:%(message)s',
13 | datefmt='%m/%d/%Y %H:%M:%S')
14 | logging.info('Starting index update.')
15 | update_index = subprocess.Popen(['sudo', '-u', 'www-data',
16 | domain_dir+'bin/python',
17 | appdir+'manage.py', 'update_index'],
18 | stdout=subprocess.PIPE,
19 | stderr=subprocess.STDOUT)
20 | update_index.wait()
21 | apachereload = subprocess.Popen(['sudo',
22 | '/etc/init.d/apache2',
23 | 'force-reload'],
24 | stdout=subprocess.PIPE,
25 | stderr=subprocess.STDOUT)
26 | apachereload.wait()
27 | if not any((update_index.returncode, apachereload.returncode)):
28 | logging.info('Index successfully updated.')
29 | else:
30 | subs = [update_index, apachereload]
31 | logging.error('**INDEX UPDATE FAILED**')
32 | logging.error('The following exit codes were returned:')
33 | logging.error('- update_index: %s' % update_index.returncode)
34 | logging.error('- apachereload: %s' % apachereload.returncode)
35 | for sub in subs:
36 | if sub.returncode:
37 | logging.error('Error information:')
38 | logging.error('stdout: %s' % sub.communicate()[0])
39 | logging.error('stderr: %s' % sub.communicate()[1])
40 |
41 | if __name__ == '__main__':
42 | update()
43 |
44 |
--------------------------------------------------------------------------------
/yuk/static/bg-stripe.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdeboard/yukproj/d1ee5b2ad957e8a01a11ba2ea3d325796412812f/yuk/static/bg-stripe.gif
--------------------------------------------------------------------------------
/yuk/static/black-stripe.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdeboard/yukproj/d1ee5b2ad957e8a01a11ba2ea3d325796412812f/yuk/static/black-stripe.gif
--------------------------------------------------------------------------------
/yuk/static/css/blueprint/icons/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdeboard/yukproj/d1ee5b2ad957e8a01a11ba2ea3d325796412812f/yuk/static/css/blueprint/icons/cross.png
--------------------------------------------------------------------------------
/yuk/static/css/blueprint/icons/key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdeboard/yukproj/d1ee5b2ad957e8a01a11ba2ea3d325796412812f/yuk/static/css/blueprint/icons/key.png
--------------------------------------------------------------------------------
/yuk/static/css/blueprint/icons/tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdeboard/yukproj/d1ee5b2ad957e8a01a11ba2ea3d325796412812f/yuk/static/css/blueprint/icons/tick.png
--------------------------------------------------------------------------------
/yuk/static/css/blueprint/ie.css:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------
2 |
3 |
4 | Blueprint CSS Framework 1.0
5 | http://blueprintcss.org
6 |
7 | * Copyright (c) 2007-Present. See LICENSE for more info.
8 | * See README for instructions on how to use Blueprint.
9 | * For credits and origins, see AUTHORS.
10 | * This is a compressed file. See the sources in the 'src' directory.
11 |
12 | ----------------------------------------------------------------------- */
13 |
14 | /* ie.css */
15 | body {text-align:center;}
16 | .container {text-align:left;}
17 | * html .column, * html .span-1, * html .span-2, * html .span-3, * html .span-4, * html .span-5, * html .span-6, * html .span-7, * html .span-8, * html .span-9, * html .span-10, * html .span-11, * html .span-12, * html .span-13, * html .span-14, * html .span-15, * html .span-16, * html .span-17, * html .span-18, * html .span-19, * html .span-20, * html .span-21, * html .span-22, * html .span-23, * html .span-24 {display:inline;overflow-x:hidden;}
18 | * html legend {margin:0px -8px 16px 0;padding:0;}
19 | sup {vertical-align:text-top;}
20 | sub {vertical-align:text-bottom;}
21 | html>body p code {*white-space:normal;}
22 | hr {margin:-8px auto 11px;}
23 | img {-ms-interpolation-mode:bicubic;}
24 | .clearfix, .container {display:inline-block;}
25 | * html .clearfix, * html .container {height:1%;}
26 | fieldset {padding-top:0;}
27 | legend {margin-top:-0.2em;margin-bottom:1em;margin-left:-0.5em;}
28 | textarea {overflow:auto;}
29 | label {vertical-align:middle;position:relative;top:-0.25em;}
30 | input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;}
31 | input.text:focus, input.title:focus {border-color:#666;}
32 | input.text, input.title, textarea, select {margin:0.5em 0;}
33 | input.checkbox, input.radio {position:relative;top:.25em;}
34 | form.inline div, form.inline p {vertical-align:middle;}
35 | form.inline input.checkbox, form.inline input.radio, form.inline input.button, form.inline button {margin:0.5em 0;}
36 | button, input.button {position:relative;top:0.25em;}
--------------------------------------------------------------------------------
/yuk/static/css/blueprint/print.css:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------
2 |
3 |
4 | Blueprint CSS Framework 1.0
5 | http://blueprintcss.org
6 |
7 | * Copyright (c) 2007-Present. See LICENSE for more info.
8 | * See README for instructions on how to use Blueprint.
9 | * For credits and origins, see AUTHORS.
10 | * This is a compressed file. See the sources in the 'src' directory.
11 |
12 | ----------------------------------------------------------------------- */
13 |
14 | /* print.css */
15 | body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;}
16 | .container {background:none;}
17 | hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;}
18 | hr.space {background:#fff;color:#fff;visibility:hidden;}
19 | h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;}
20 | code {font:.9em "Courier New", Monaco, Courier, monospace;}
21 | a img {border:none;}
22 | p img.top {margin-top:0;}
23 | blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;}
24 | .small {font-size:.9em;}
25 | .large {font-size:1.1em;}
26 | .quiet {color:#999;}
27 | .hide {display:none;}
28 | a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;}
29 | a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;}
--------------------------------------------------------------------------------
/yuk/static/css/blueprint/readme.txt:
--------------------------------------------------------------------------------
1 | Buttons
2 |
3 | * Gives you great looking CSS buttons, for both and .
4 | * Demo: particletree.com/features/rediscovering-the-button-element
5 |
6 |
7 | Credits
8 | ----------------------------------------------------------------
9 |
10 | * Created by Kevin Hale [particletree.com]
11 | * Adapted for Blueprint by Olav Bjorkoy [bjorkoy.com]
12 |
13 |
14 | Usage
15 | ----------------------------------------------------------------
16 |
17 | 1) Add this plugin to lib/settings.yml.
18 | See compress.rb for instructions.
19 |
20 | 2) Use the following HTML code to place the buttons on your site:
21 |
22 |
23 | Save
24 |
25 |
26 |
27 | Change Password
28 |
29 |
30 |
31 | Cancel
32 |
33 |
--------------------------------------------------------------------------------
/yuk/static/css/blueprint/screen.css:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------------------------------
2 |
3 |
4 | Blueprint CSS Framework 1.0
5 | http://blueprintcss.org
6 |
7 | * Copyright (c) 2007-Present. See LICENSE for more info.
8 | * See README for instructions on how to use Blueprint.
9 | * For credits and origins, see AUTHORS.
10 | * This is a compressed file. See the sources in the 'src' directory.
11 |
12 | ----------------------------------------------------------------------- */
13 |
14 | /* reset.css */
15 | html {margin:0;padding:0;border:0;}
16 | body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, dialog, figure, footer, header, hgroup, nav, section {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;}
17 | article, aside, dialog, figure, footer, header, hgroup, nav, section {display:block;}
18 | body {line-height:1.5;background:white;}
19 | table {border-collapse:separate;border-spacing:0;}
20 | caption, th, td {text-align:left;font-weight:normal;float:none !important;}
21 | table, th, td {vertical-align:middle;}
22 | blockquote:before, blockquote:after, q:before, q:after {content:'';}
23 | blockquote, q {quotes:"" "";}
24 | a img {border:none;}
25 | :focus {outline:0;}
26 |
27 | /* typography.css */
28 | html {font-size:100.01%;}
29 | body {font-size:75%;color:#222;background:white;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;}
30 | h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#333333;}
31 | h1 {font-size:7em;line-height:1;}
32 | h2 {font-size:14px;margin-bottom:0.75em;font-weight:bold;color:#0B2B44;}
33 | h3 {font-size:1.5em;line-height:1;margin-bottom:.1em;}
34 | h4 {font-size:4em;line-height:.5;margin-bottom:.1em;color:#c6d729;}
35 | h5 {font-size:2em;font-weight:bold;margin-bottom:.1em;}
36 | h6 {font-size:1em;font-weight:bold;margin-bottom:1.5em;}
37 | h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;}
38 | p {margin:0 0 1.5em;}
39 | .left {float:left !important;}
40 | p .left {margin:1.5em 1.5em 1.5em 0;padding:0;}
41 | .right {float:right !important;}
42 | p .right {margin:1.5em 0 1.5em 1.5em;padding:0;}
43 | a {color:#0b2b44;text-decoration:none;}
44 | a:focus, a:hover {color:#a92323;}
45 | .arrow {font-weight:bold;font-size:16px;color:#9c3839;}
46 | blockquote {margin:1.5em;color:#666666;font-style:italic;}
47 | strong font, dfn font {weight:bold;color:#111111;}
48 | em, dfn {font-style:italic;}
49 | sup, sub {line-height:0;}
50 | abbr, acronym {border-bottom:1px dotted #666666;}
51 | address {margin:0 0 1.5em;font-style:italic;}
52 | del {color:#666666;}
53 | pre {margin:1.5em 0;white-space:pre;}
54 | pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}
55 | li ul, li ol {margin:0;}
56 | ul, ol {margin:0 1.5em 1.5em 0;padding-left:1.5em;}
57 | ul {list-style-type:disc;}
58 | ol {list-style-type:decimal;}
59 | dl {margin:0 0 1.5em 0;}
60 | dl dt {font-weight:bold;}
61 | dd {margin-left:1.5em;}
62 | table {margin-bottom:1.4em;width:100%;}
63 | th {font-weight:bold;}
64 | thead th {background:#c3d9ff;}
65 | th, td, caption {padding:4px 10px 4px 5px;}
66 | tbody tr:nth-child(even) td, tbody tr.even td {background:#e5ecf9;}
67 | tfoot {font-style:italic;}
68 | caption {background:#eee;}
69 | .small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;}
70 | .large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;}
71 | .hide {display:none;}
72 | .quiet {color:#666666;}
73 | .loud {color:#000;}
74 | .highlight {background:#ff0;}
75 | .added {background:#060;color:#fff;}
76 | .removed {background:#900;color:#fff;}
77 | .first {margin-left:0;padding-left:0;}
78 | .last {margin-right:0;padding-right:0;}
79 | .top {margin-top:0;padding-top:0;}
80 | .bottom {margin-bottom:0;padding-bottom:0;}
81 |
82 | /* forms.css */
83 | label {font-weight:bold;}
84 | fieldset {padding:0 1.4em 1.4em 1.4em;margin:0 0 1.5em 0;border:1px solid #ccc;}
85 | legend {font-weight:bold;font-size:1.2em;margin-top:-0.2em;margin-bottom:1em;}
86 | fieldset, #IE8#HACK {padding-top:1.4em;}
87 | legend, #IE8#HACK {margin-top:0;margin-bottom:0;}
88 | input[type=text], input[type=password], input.text, input.title, textarea {background-color:white;border:1px solid #bbbbbb;}
89 | input[type=text]:focus, input[type=password]:focus, input.text:focus, input.title:focus, textarea:focus {border-color:#666666;}
90 | select {background-color:white;border-width:1px;border-style:solid;}
91 | input[type=text], input[type=password], input.text, input.title, textarea, select {margin:0.5em 0;}
92 | input.text, input.title {width:300px;padding:5px;}
93 | input.title {font-size:1.5em;}
94 | textarea {width:390px;height:250px;padding:5px;}
95 | form.inline {line-height:3;}
96 | form.inline p {margin-bottom:0;}
97 | .error, .alert, .notice, .success, .info {padding:0.8em;margin-bottom:1em;border:2px solid #ddd;}
98 | .error, .alert {background:#fbe3e4;color:#8a1f11;border-color:#fbe3e4;}
99 | .notice {background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4;}
100 | .success {background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4;}
101 | .info {background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4;}
102 | .error a, .alert a {color:#8a1f11;}
103 | .notice a {color:#514721;}
104 | .success a {color:#264409;}
105 | .info a {color:#205791;}
106 |
107 | /* grid.css */
108 | .container {width:950px;margin:0 auto;}
109 | .showgrid {background:url(src/grid.png);}
110 | .column, .span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9, .span-10, .span-11, .span-12, .span-13, .span-14, .span-15, .span-16, .span-17, .span-18, .span-19, .span-20, .span-21, .span-22, .span-23, .span-24 {float:left;margin-right:10px;}
111 | .last {margin-right:0;}
112 | .span-1 {width:30px;}
113 | .span-2 {width:70px;}
114 | .span-3 {width:110px;}
115 | .span-4 {width:150px;}
116 | .span-5 {width:190px;}
117 | .span-6 {width:230px;}
118 | .span-7 {width:270px;}
119 | .span-8 {width:310px;}
120 | .span-9 {width:350px;}
121 | .span-10 {width:390px;}
122 | .span-11 {width:430px;}
123 | .span-12 {width:470px;}
124 | .span-13 {width:510px;}
125 | .span-14 {width:550px;}
126 | .span-15 {width:590px;}
127 | .span-16 {width:630px;}
128 | .span-17 {width:670px;}
129 | .span-18 {width:710px;}
130 | .span-19 {width:750px;}
131 | .span-20 {width:790px;}
132 | .span-21 {width:830px;}
133 | .span-22 {width:870px;}
134 | .span-23 {width:910px;}
135 | .span-24 {width:950px;margin-right:0;}
136 | input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px;border-right-width:1px;padding-left:5px;padding-right:5px;}
137 | input.span-1, textarea.span-1 {width:18px;}
138 | input.span-2, textarea.span-2 {width:58px;}
139 | input.span-3, textarea.span-3 {width:98px;}
140 | input.span-4, textarea.span-4 {width:138px;}
141 | input.span-5, textarea.span-5 {width:178px;}
142 | input.span-6, textarea.span-6 {width:218px;}
143 | input.span-7, textarea.span-7 {width:258px;}
144 | input.span-8, textarea.span-8 {width:298px;}
145 | input.span-9, textarea.span-9 {width:338px;}
146 | input.span-10, textarea.span-10 {width:378px;}
147 | input.span-11, textarea.span-11 {width:418px;}
148 | input.span-12, textarea.span-12 {width:458px;}
149 | input.span-13, textarea.span-13 {width:498px;}
150 | input.span-14, textarea.span-14 {width:538px;}
151 | input.span-15, textarea.span-15 {width:578px;}
152 | input.span-16, textarea.span-16 {width:618px;}
153 | input.span-17, textarea.span-17 {width:658px;}
154 | input.span-18, textarea.span-18 {width:698px;}
155 | input.span-19, textarea.span-19 {width:738px;}
156 | input.span-20, textarea.span-20 {width:778px;}
157 | input.span-21, textarea.span-21 {width:818px;}
158 | input.span-22, textarea.span-22 {width:858px;}
159 | input.span-23, textarea.span-23 {width:898px;}
160 | input.span-24, textarea.span-24 {width:938px;}
161 | .append-1 {padding-right:40px;}
162 | .append-2 {padding-right:80px;}
163 | .append-3 {padding-right:120px;}
164 | .append-4 {padding-right:160px;}
165 | .append-5 {padding-right:200px;}
166 | .append-6 {padding-right:240px;}
167 | .append-7 {padding-right:280px;}
168 | .append-8 {padding-right:320px;}
169 | .append-9 {padding-right:360px;}
170 | .append-10 {padding-right:400px;}
171 | .append-11 {padding-right:440px;}
172 | .append-12 {padding-right:480px;}
173 | .append-13 {padding-right:520px;}
174 | .append-14 {padding-right:560px;}
175 | .append-15 {padding-right:600px;}
176 | .append-16 {padding-right:640px;}
177 | .append-17 {padding-right:680px;}
178 | .append-18 {padding-right:720px;}
179 | .append-19 {padding-right:760px;}
180 | .append-20 {padding-right:800px;}
181 | .append-21 {padding-right:840px;}
182 | .append-22 {padding-right:880px;}
183 | .append-23 {padding-right:920px;}
184 | .prepend-1 {padding-left:40px;}
185 | .prepend-2 {padding-left:80px;}
186 | .prepend-3 {padding-left:120px;}
187 | .prepend-4 {padding-left:160px;}
188 | .prepend-5 {padding-left:200px;}
189 | .prepend-6 {padding-left:240px;}
190 | .prepend-7 {padding-left:280px;}
191 | .prepend-8 {padding-left:320px;}
192 | .prepend-9 {padding-left:360px;}
193 | .prepend-10 {padding-left:400px;}
194 | .prepend-11 {padding-left:440px;}
195 | .prepend-12 {padding-left:480px;}
196 | .prepend-13 {padding-left:520px;}
197 | .prepend-14 {padding-left:560px;}
198 | .prepend-15 {padding-left:600px;}
199 | .prepend-16 {padding-left:640px;}
200 | .prepend-17 {padding-left:680px;}
201 | .prepend-18 {padding-left:720px;}
202 | .prepend-19 {padding-left:760px;}
203 | .prepend-20 {padding-left:800px;}
204 | .prepend-21 {padding-left:840px;}
205 | .prepend-22 {padding-left:880px;}
206 | .prepend-23 {padding-left:920px;}
207 | .border {padding-right:4px;margin-right:5px;border-right:1px solid #ddd;}
208 | .colborder {padding-right:24px;margin-right:25px;border-right:1px solid #ddd;}
209 | .pull-1 {margin-left:-40px;}
210 | .pull-2 {margin-left:-80px;}
211 | .pull-3 {margin-left:-120px;}
212 | .pull-4 {margin-left:-160px;}
213 | .pull-5 {margin-left:-200px;}
214 | .pull-6 {margin-left:-240px;}
215 | .pull-7 {margin-left:-280px;}
216 | .pull-8 {margin-left:-320px;}
217 | .pull-9 {margin-left:-360px;}
218 | .pull-10 {margin-left:-400px;}
219 | .pull-11 {margin-left:-440px;}
220 | .pull-12 {margin-left:-480px;}
221 | .pull-13 {margin-left:-520px;}
222 | .pull-14 {margin-left:-560px;}
223 | .pull-15 {margin-left:-600px;}
224 | .pull-16 {margin-left:-640px;}
225 | .pull-17 {margin-left:-680px;}
226 | .pull-18 {margin-left:-720px;}
227 | .pull-19 {margin-left:-760px;}
228 | .pull-20 {margin-left:-800px;}
229 | .pull-21 {margin-left:-840px;}
230 | .pull-22 {margin-left:-880px;}
231 | .pull-23 {margin-left:-920px;}
232 | .pull-24 {margin-left:-960px;}
233 | .pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;}
234 | .push-1 {margin:0 -40px 1.5em 40px;}
235 | .push-2 {margin:0 -80px 1.5em 80px;}
236 | .push-3 {margin:0 -120px 1.5em 120px;}
237 | .push-4 {margin:0 -160px 1.5em 160px;}
238 | .push-5 {margin:0 -200px 1.5em 200px;}
239 | .push-6 {margin:0 -240px 1.5em 240px;}
240 | .push-7 {margin:0 -280px 1.5em 280px;}
241 | .push-8 {margin:0 -320px 1.5em 320px;}
242 | .push-9 {margin:0 -360px 1.5em 360px;}
243 | .push-10 {margin:0 -400px 1.5em 400px;}
244 | .push-11 {margin:0 -440px 1.5em 440px;}
245 | .push-12 {margin:0 -480px 1.5em 480px;}
246 | .push-13 {margin:0 -520px 1.5em 520px;}
247 | .push-14 {margin:0 -560px 1.5em 560px;}
248 | .push-15 {margin:0 -600px 1.5em 600px;}
249 | .push-16 {margin:0 -640px 1.5em 640px;}
250 | .push-17 {margin:0 -680px 1.5em 680px;}
251 | .push-18 {margin:0 -720px 1.5em 720px;}
252 | .push-19 {margin:0 -760px 1.5em 760px;}
253 | .push-20 {margin:0 -800px 1.5em 800px;}
254 | .push-21 {margin:0 -840px 1.5em 840px;}
255 | .push-22 {margin:0 -880px 1.5em 880px;}
256 | .push-23 {margin:0 -920px 1.5em 920px;}
257 | .push-24 {margin:0 -960px 1.5em 960px;}
258 | .push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:left;position:relative;}
259 | div.prepend-top, .prepend-top {margin-top:1.5em;}
260 | div.append-bottom, .append-bottom {margin-bottom:1.5em;}
261 | .box {padding:1.5em;margin-bottom:1.5em;background:#e5eCf9;}
262 | hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margin:0 0 1.45em;border:none;}
263 | hr.space {background:#fff;color:#fff;visibility:hidden;}
264 | .clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;}
265 | .clearfix, .container {display:block;}
266 | .clear {clear:both;}
267 |
268 | /* fancy-type */
269 | p + p {text-indent:2em;margin-top:-1.5em;}
270 | form p + p {text-indent:0;}
271 | .alt {color:#666;font-family:"Warnock Pro", "Goudy Old Style","Palatino","Book Antiqua", Georgia, serif;font-style:italic;font-weight:normal;}
272 | .dquo {margin-left:-.5em;}
273 | p.incr, .incr p {font-size:10px;line-height:1.44em;margin-bottom:1.5em;}
274 | .caps {font-variant:small-caps;letter-spacing:1px;text-transform:lowercase;font-size:1.2em;line-height:1%;font-weight:bold;padding:0 2px;}
275 |
276 | /* buttons */
277 | a.button, button {width:auto;overflow:visible;padding:4px 10px 3px 7px;display:block;float:left;margin:0.7em 0.5em 0.7em 0;padding:5px 10px 5px 7px;font-family:"Lucida Grande", Tahoma, Arial, Verdana, sans-serf;font-size:100%;font-weight:bold;line-height:130%;text-decoration:none;color:#333333;border:solid 1px #538312;background:#d8e36c;background-image:-webkit-gradient(linear, left top, left bottom, from(#d8e36c), to(#c6d729));background-image:-moz-linear-gradient(top, #d8e36c, #c6d729);cursor:pointer;-webkit-border-radius:.5em;-moz-border-radius:.5 em;border-radius:.5em;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.3);box-shadow:0 1px 3px rgba(0, 0, 0, 0.3);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='$dark_btn_gradient', endColorstr='$light_btn_gradient');}
278 | a.button img, button img {margin:0 3px -3px 0;padding:0;border:none;width:16px;height:16px;float:none;}
279 | a.button:hover, button:hover {background-color:#d8e36c;border:solid 1px #538312;}
280 | a.button:active, button:active {background-color:#d8e36c;}
281 | button[type] {padding:4px 10px 4px 7px;line-height:17px;}
282 | .positive:hover {background-color:#c6d729;}
283 | .positive:active {background-color:#529214;border:solid 1px #538312;color:#0f3b5f;}
284 | .negative:hover {background-color:#c6d729;}
285 | .negative:active {background-color:#d12f19;color:white;}
286 |
287 | /* y_screen.css */
288 | div .tags {font-size:1em;color:#a92323;}
289 | div .tags:hover, div .tags:focus {color:#333333;border-bottom:1px dotted;}
290 | div .welcome {font-size:10em;font-family:"Permanent Marker", Arial, Helvetica, sans-serif;color:#9c3839;text-align:center;}
291 | a .link {font-size:.75em;color:#a92323;}
292 | a:hover, a:focus {color:#9c3839;border-bottom:1px dotted;}
293 | .logo, .lheader {font-family:"Permanent Marker", Arial, Helvetica, sans-serif;text-shadow:1px 1px 1px #222;text-decoration:none;color:white;}
294 | .lheader {text-align:center;}
295 | #userheader {font-family:"Permanent Marker", Arial, Helvetica, sans-serif;font-size:18px;font-weight:bold;color:#9c3839;}
296 | .about {text-shadow:1px 1px 1px #222;text-align:justify;color:white;font-size:18px;font-weight:bold;}
297 | .accentheader {color:#9c3839;font-family:"Permanent Marker", Arial, Helvetica, sans-serif;font-size:18px;}
298 | .shadow {text-shadow:1px 1px 1px #666;}
299 | .linkhead {color:white;font-size:14px;font-weight:bold;}
300 | body #header {background-color:#c6d729;height:124px;-webkit-box-shadow:0 3px 5px rgba(0, 0, 0, 0.5);-moz-box-shadow:0 3px 5px rgba(0, 0, 0, 0.5);}
301 | body #subheader {background-color:#0f3b5f;height:100px;-webkit-box-shadow:0 3px 3px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 3px rgba(0, 0, 0, 0.3);}
302 | body #top {height:250px;background:#c6d729;-webkit-box-shadow:0 3px 10px rgba(0, 0, 0, 0.7);-moz-box-shadow:0 3px 10px rgba(0, 0, 0, 0.7);}
303 | body #stripe {height:210px;background:#0f3b5f;-webkit-box-shadow:0 3px 5px rgba(0, 0, 0, 0.7);-moz-box-shadow:0 3px 5px rgba(0, 0, 0, 0.7);}
--------------------------------------------------------------------------------
/yuk/static/green-stripe.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdeboard/yukproj/d1ee5b2ad957e8a01a11ba2ea3d325796412812f/yuk/static/green-stripe.gif
--------------------------------------------------------------------------------
/yuk/templates/401.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block body %}
3 | You do not have permission to access this item! Sry m8
4 |
8 | {% endblock content %}
9 |
--------------------------------------------------------------------------------
/yuk/templates/404.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block body %}
3 | {% url yuk.views.redir_to_profile as the_url %}
4 | Whoops!
5 |
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/yuk/templates/500.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block body %}
3 | {% url yuk.views.redir_to_profile as the_url %}
4 | Whoops!
5 |
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/yuk/templates/base.html:
--------------------------------------------------------------------------------
1 | {% url django.contrib.auth.views.logout_then_login as logout_url %}
2 | {% url yuk.views.profile uname=user as the_url %}
3 | {% url yuk.views.rss_import as rss_import %}
4 | {% url yuk.views.new_url as new_bookmark %}
5 | {% url yuk.views.export as export_url %}
6 | {% url yuk.views.import_text as import_url %}
7 | {% url yuk.views.new_note as new_note %}
8 | {% url yuk.views.new_quote as new_quote %}
9 |
10 |
11 |
14 |
16 |
18 |
20 |
24 |
25 | Yuk!{% block title %}{% endblock %}
26 |
27 |
56 |
57 | {% block body %}
58 |
59 | {% block content %}
60 | {% endblock content %}
61 |
62 |
63 | {% block side %}
64 | {% if user.is_authenticated %}
65 |
74 |
75 |
76 | Use the Yuk! bookmarklet to add links to your Yuk! profile from anywhere. Just click it to save the URL of your current page. Click and drag the bookmarklet below to your browser's bookmark bar:
77 |
79 |
80 | {% else %}
81 |
82 |
Some website functionality, like tag filtering, will not work unless you're logged in as a registered user! It's easy to register, so what are you waiting for?
83 |
84 |
85 | {% endif %}
86 | {% endblock side %}
87 |
88 |
89 | {% endblock body %}
90 |
91 |
92 |
134 |
135 |
147 |
148 |
--------------------------------------------------------------------------------
/yuk/templates/bm_login.html:
--------------------------------------------------------------------------------
1 | {% extends 'bookmarklet_add.html' %}
2 | {% block content %}
3 |
13 |
14 | {% endblock content %}
15 |
--------------------------------------------------------------------------------
/yuk/templates/bookmark_import.html:
--------------------------------------------------------------------------------
1 | {% extends 'user_profile.html' %}
2 | {% block title %}
3 | - Import From File
4 | {% endblock title %}
5 | {% block content %}
6 |
9 |
10 | Yuk! supports importing text files in the pinboard/del.icio.us format. If you have another format you'd like to see added for import, contact me.
11 |
12 |
20 | {% endblock content %}
21 |
--------------------------------------------------------------------------------
/yuk/templates/bookmarklet_add.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
9 |
13 |
14 | Yuk!
15 |
16 |
17 | {% block content %}
18 |
31 | {% endblock content %}
32 |
33 |
34 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/yuk/templates/del_url.html:
--------------------------------------------------------------------------------
1 | {% extends 'user_profile.html' %}
2 | {% block modify %}
3 | Are you sure you want to delete {{ url }}, {{ user }}?
4 | {% endblock modify %}
--------------------------------------------------------------------------------
/yuk/templates/edit_url.html:
--------------------------------------------------------------------------------
1 | {% extends 'user_profile.html' %}
2 | {% block title %}
3 | - Edit Bookmark
4 | {% endblock title %}
5 | {% block content %}
6 |
24 | {% endblock %}
25 |
26 |
--------------------------------------------------------------------------------
/yuk/templates/export.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Yuk! Bookmarks
4 | {% for url in urls %}
5 |
{{ url.url_name }} {% endfor %}
6 |
7 |
--------------------------------------------------------------------------------
/yuk/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 | {% url django.contrib.auth.views.logout as logout_url %}
4 | {% url yuk.views.new_url as new_url_url %}
5 |
6 | {% block urlform %}
7 |
8 |
9 |
10 | {% for i in urls %}
11 | {% if i.url_name %}
12 |
{{ i.url_name }}
13 | {% else %}
14 | {{ i }}
15 | {% endif %}
16 |
17 |
18 |
19 | {% url yuk.views.edit_url uname=user url_id=i.id as edit_url %}
20 | {% url yuk.views.del_url uname=user url_id=i.id as del_url %}
21 |
22 | {% endfor %}
23 |
24 | {% block modify %}
25 | {% endblock modify %}
26 |
27 |
28 | {% endblock urlform %}
29 | {% endblock content %}
30 |
--------------------------------------------------------------------------------
/yuk/templates/landing.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
A "yukmark" is a way to store and retrieve the sites, information and big ideas you find on the web quickly and painlessly. It's a lot like the bookmarks in your browser, but available from any computer, any browser, anywhere!
20 |
21 |
22 |
23 |
24 |
37 |
38 |
39 |
56 |
57 |
58 |
59 |
60 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/yuk/templates/new_item.html:
--------------------------------------------------------------------------------
1 | {% extends 'user_profile.html' %}
2 | {% block title %}
3 | - New {{ type }}
4 | {% endblock %}
5 | {% block content %}
6 |
30 | {% endblock %}
31 |
--------------------------------------------------------------------------------
/yuk/templates/registration/activate.html:
--------------------------------------------------------------------------------
1 | Activation failed
--------------------------------------------------------------------------------
/yuk/templates/registration/activation_email.txt:
--------------------------------------------------------------------------------
1 | You really suck. Seriously.
2 |
--------------------------------------------------------------------------------
/yuk/templates/registration/activation_email_subject.txt:
--------------------------------------------------------------------------------
1 | You suck faggot
2 |
--------------------------------------------------------------------------------
/yuk/templates/registration/login.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 | {% url django.contrib.auth.views.login as do_login %}
4 | {% url registration_register as reg %}
5 | {% if messages %}
6 |
7 |
8 | {% for message in messages %}
9 | {{ message }}
10 | {% endfor %}
11 |
12 |
13 | {% endif %}
14 |
20 |
21 | {% block side %}
22 | {% endblock side %}
23 |
24 | {% endblock content %}
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/yuk/templates/registration/logout.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block auth_action %}
3 | {% url django.contrib.auth.views.login as login_url %}
4 | Bye!
5 |
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/yuk/templates/registration/registration_complete.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 | Registration complete
4 | {% url yuk.views.profile uname=user as the_url %}
5 |
6 | {% endblock %}
7 |
--------------------------------------------------------------------------------
/yuk/templates/registration/registration_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
21 | {% endblock %}
22 |
--------------------------------------------------------------------------------
/yuk/templates/rss_import.html:
--------------------------------------------------------------------------------
1 | {% extends 'user_profile.html' %}
2 | {% block content %}
3 |
13 | {% endblock content %}
14 |
15 |
--------------------------------------------------------------------------------
/yuk/templates/search/indexes/yuk/item_text.txt:
--------------------------------------------------------------------------------
1 | {{ object.displays }}
2 | {{ object.user }}
3 | {{ object.description }}
4 | {{ object.tags.all }}
5 | {{ object.url }}
6 | {{ object.id }}
7 |
--------------------------------------------------------------------------------
/yuk/templates/search/indexes/yuk/url_text.txt:
--------------------------------------------------------------------------------
1 | {{ object.url_name }}
2 | {{ object.user.username }}
3 | {{ object.url_desc }}
4 | {{ object.tags.all }}
5 | {{ object.url }}
6 | {{ object.id }}
7 |
--------------------------------------------------------------------------------
/yuk/templates/search/search.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load taggit_extras %}
3 | {% load timesince %}
4 | {% block header %}
5 | {% endblock header %}
6 | {% block content %}
7 | {% url yuk.views.profile uname=user as profile_url %}
8 | {% if messages %}
9 |
10 |
11 | {% for message in messages %}
12 | {{ message }}
13 |
14 | {% endfor %}
15 |
16 |
17 | {% endif %}
18 | {% for result in results %}
19 | {% if result.object.displays %}
20 |
21 |
25 | {% else %}
26 |
27 |
31 | {% endif %}
32 |
33 |
38 |
{{ result.object.url_desc }}
39 |
40 | {% url yuk.views.edit_item uname=user item_id=result.object.id as edit_url %}
41 | {% url yuk.views.del_item uname=user item_id=result.object.id as del_url %}
42 |
43 |
44 | {{ result.object.date_created|timedelta }}
45 |
46 |
47 | {% if user = result.object.user %}
48 |
54 | {% endif %}
55 |
56 | {% if not forloop.last %}
57 |
59 |
60 |
61 | {% endif %}
62 |
63 |
64 | {% endfor %}
65 | {% url yuk.views.profile uname=user.username as the_url %}
66 |
Return to profile
67 |
68 | {% endblock content %}
69 |
70 |
--------------------------------------------------------------------------------
/yuk/templates/search/search_form.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block search %}
3 |
11 | {% endblock search %}
12 |
--------------------------------------------------------------------------------
/yuk/templates/stored.html:
--------------------------------------------------------------------------------
1 | {% extends "new_url.html" %}
2 | {% block body %}
3 | {% if form.errors %}
4 |
{{ form.errors }}
5 |
--------------------------------------------------------------------------------
/yuk/templates/tag.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load taggit_extras %}
3 | {% load timesince %}
4 | {% block content %}
5 |
6 | {% if messages %}
7 |
8 |
9 | {% for message in messages %}
10 | {{ message }}
11 |
12 | {% endfor %}
13 |
14 |
15 | {% endif %}
16 |
17 | {% if not results %}
18 |
No results for that tag.
19 | {% endif %}
20 |
22 | {% for result in results %}
23 |
24 | {% if result.description %}
25 |
26 |
30 | {% else %}
31 |
32 |
35 | {% endif %}
36 |
37 |
41 |
{{ result.url_desc }}
42 |
43 | {% url yuk.views.edit_url uname=user url_id=result.id as edit_url %}
44 | {% url yuk.views.del_url uname=user url_id=result.id as del_url %}
45 |
46 |
47 | {{ result.date_created|timedelta }}
48 |
49 |
50 | {% if user.username = uname %}
51 |
57 | {% endif %}
58 |
59 | {% if not forloop.last %}
60 |
62 |
63 |
64 | {% endif %}
65 |
66 |
67 | {% endfor %}
68 | {% url yuk.views.profile uname=user.username as the_url %}
69 |
Return to profile
70 |
71 | {% endblock content %}
72 |
73 |
--------------------------------------------------------------------------------
/yuk/templates/unknown.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% block content %}
3 |
We couldn't find the page you requested.
4 | {% if user.is_authenticated %}
5 | {% url yuk.views.profile uname=user as the_url %}
6 |
7 | {% else %}
8 |
Return to Yuk!
9 | {% endif %}
10 | {% endblock content %}
--------------------------------------------------------------------------------
/yuk/templates/user_profile.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load taggit_extras %}
3 | {% load timesince %}
4 | {% block header %}
5 | {% endblock header %}
6 | {% block content %}
7 | {% if messages %}
8 |
9 |
10 | {% for message in messages %}
11 | {{ message }}
12 |
13 | {% endfor %}
14 |
15 |
16 | {% endif %}
17 | {% if not results %}
18 |
Welcome!
19 |
To get started, use your mouse to drag the button that says "Yuk! popup" to your bookmark bar, and browse the web like usual. When you find a site you want to bookmark, just click the "Yuk! popup" button and you'll be set.
20 | {% endif %}
21 | {% for result in results %}
22 |
23 |
30 |
{{ result.description }}
31 |
32 |
33 |
41 |
42 | {% url yuk.views.edit_item uname=user item_id=result.id as edit_url %}
43 | {% url yuk.views.del_item uname=user url_id=result.id as del_item %}
44 |
45 |
46 | {{ result.date_created|timedelta }}
47 |
48 |
49 | {% if user.username = uname %}
50 |
56 | {% endif %}
57 |
58 | {% if not forloop.last %}
59 |
61 |
62 |
63 | {% endif %}
64 |
65 |
66 | {% endfor %}
67 | {% endblock content %}
68 |
69 |
--------------------------------------------------------------------------------
/yuk/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mattdeboard/yukproj/d1ee5b2ad957e8a01a11ba2ea3d325796412812f/yuk/templatetags/__init__.py
--------------------------------------------------------------------------------
/yuk/templatetags/timesince.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.utils.timesince import timesince
3 | from datetime import datetime
4 |
5 | register = template.Library()
6 |
7 | def timedelta(value, arg=None):
8 | if not value:
9 | return ''
10 | if arg:
11 | cmp = arg
12 | else:
13 | cmp = datetime.now()
14 | if value > cmp:
15 | return "in %s" % timesince(cmp,value)
16 | else:
17 | return "%s ago" % timesince(value,cmp)
18 |
19 | register.filter('timedelta',timedelta)
20 |
--------------------------------------------------------------------------------
/yuk/tests.py:
--------------------------------------------------------------------------------
1 | """
2 | This file demonstrates two different styles of tests (one doctest and one
3 | unittest). These will both pass when you run "manage.py test".
4 |
5 | Replace these with more appropriate tests for your application.
6 | """
7 |
8 | from django.test import TestCase
9 |
10 | class SimpleTest(TestCase):
11 | def test_basic_addition(self):
12 | """
13 | Tests that 1 + 1 always equals 2.
14 | """
15 | self.failUnlessEqual(1 + 1, 2)
16 |
17 | __test__ = {"doctest": """
18 | Another way to test that 1 + 1 is equal to 2.
19 |
20 | >>> 1 + 1 == 2
21 | True
22 | """}
23 |
24 |
--------------------------------------------------------------------------------
/yuk/urls.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | from django.contrib import admin
5 | from django.conf.urls.defaults import *
6 |
7 | from yuk import localsettings
8 |
9 | admin.autodiscover()
10 |
11 | urlpatterns = patterns('',
12 | #(r'^admin/doc/', include('django.contrib.admindocs.urls')),
13 | (r'^admin/', include(admin.site.urls)),
14 | (r'^search/', 'yuk.views.search_results'),
15 | (r'^add_bookmark_remote/$', 'yuk.views.remote_new_url'),
16 | (r'^add_bookmark/$', 'yuk.views.new_url'),
17 | (r'^add_note/$', 'yuk.views.new_note'),
18 | (r'^add_quote/$', 'yuk.views.new_quote'),
19 | (r'^$', 'yuk.views.landing'),
20 | (r'^users/(?P
\w+)/$', 'yuk.views.redir_to_profile'),
21 | (r'^bm_login/$', 'yuk.views.bm_login'),
22 | (r'^export/$', 'yuk.views.export'),
23 | (r'^login/$', 'yuk.views.login'),
24 | (r'^accounts/', include('registration.backends.simple.urls')),
25 | (r'^logout/$', 'django.contrib.auth.views.logout_then_login',
26 | {'login_url':'/'}),
27 | (r'^accounts/profile/$', 'yuk.views.redir_to_profile'),
28 | (r'^import/$', 'yuk.views.import_text'),
29 | (r'^u:(?P\w+)/delete/$', 'yuk.views.del_item', {'item_id':None}),
30 | (r'^u:(?P\w+)/e:(?P\d+)/$', 'yuk.views.edit_item'),
31 | (r'^u:(?P\w+)/$', 'yuk.views.profile'),
32 | (r'^u:(?P\w+)/t:(?P[\w-]+)/$', 'yuk.views.tag_detail'),
33 | (r'^u:(?P\w+)/import_rss/$', 'yuk.views.rss_import'),
34 | )
35 |
36 |
37 | if os.environ['DJANGO_SETTINGS_MODULE']:
38 | urlpatterns += patterns('',
39 | (r"^site_media/(?P.*)/$",
40 | 'django.views.static.serve',
41 | {'document_root': localsettings.STATIC_DOC_ROOT})
42 | )
43 |
44 |
--------------------------------------------------------------------------------
/yuk/views.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import sys
3 |
4 | from django.views.decorators.csrf import csrf_protect
5 | from django.views.decorators.cache import never_cache
6 | from django.contrib.sites.models import get_current_site
7 | from django.conf import settings
8 | from django.contrib.auth import REDIRECT_FIELD_NAME
9 | from django.contrib.auth.decorators import login_required
10 | from django.contrib.auth import login as auth_login
11 | from django.contrib.auth.forms import AuthenticationForm
12 | from django.contrib.auth.models import User
13 | from django.core.exceptions import ObjectDoesNotExist
14 | from django.core.signals import request_finished
15 | from django.shortcuts import (render_to_response, redirect, get_object_or_404,
16 | get_list_or_404)
17 | from django.template import RequestContext
18 | from django.http import HttpResponseRedirect, HttpResponse
19 | from django.contrib import messages
20 |
21 | from haystack.query import SearchQuerySet
22 |
23 | from yuk.models import Item
24 | from yuk.forms import *
25 | from yuk.rss_module import rssdownload
26 | from yuk.scripts import import_text_file
27 |
28 |
29 | def landing(request):
30 | if request.user.is_authenticated():
31 | return redirect("yuk.views.profile", uname=request.user)
32 | return render_to_response("landing.html",
33 | context_instance=RequestContext(request))
34 |
35 | @csrf_protect
36 | @never_cache
37 | def login(request, template_name='registration/login.html',
38 | redirect_field_name=REDIRECT_FIELD_NAME,
39 | authentication_form=AuthenticationForm):
40 | """Displays the login form and handles the login action."""
41 |
42 | if request.user.is_authenticated():
43 | return redirect("yuk.views.profile", uname=request.user)
44 |
45 | redirect_to = request.REQUEST.get(redirect_field_name, '')
46 |
47 | if request.method == "POST":
48 | form = authentication_form(data=request.POST)
49 | if form.is_valid():
50 | # Light security check -- make sure redirect_to isn't garbage.
51 | if not redirect_to or ' ' in redirect_to:
52 | redirect_to = settings.LOGIN_REDIRECT_URL
53 |
54 | # Heavier security check -- redirects to http://example.com should
55 | # not be allowed, but things like /view/?param=http://example.com
56 | # should be allowed. This regex checks if there is a '//' *before* a
57 | # question mark.
58 | elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to):
59 | redirect_to = settings.LOGIN_REDIRECT_URL
60 |
61 | # Okay, security checks complete. Log the user in.
62 | auth_login(request, form.get_user())
63 |
64 | if request.session.test_cookie_worked():
65 | request.session.delete_test_cookie()
66 |
67 | return HttpResponseRedirect(redirect_to)
68 |
69 | if form.non_field_errors():
70 | messages.error(request,
71 | "Invalid user/pass combo. Is your caps lock on?",
72 | extra_tags="bad_login")
73 | if 'password' in form.errors.keys():
74 | messages.error(request,
75 | "Please enter your password.",
76 | extra_tags="bad_password")
77 |
78 |
79 |
80 | else:
81 | form = authentication_form(request)
82 |
83 | request.session.set_test_cookie()
84 |
85 | current_site = get_current_site(request)
86 |
87 | return render_to_response(template_name, {
88 | 'form': form,
89 | redirect_field_name: redirect_to,
90 | 'site': current_site,
91 | 'site_name': current_site.name,
92 | }, context_instance=RequestContext(request))
93 |
94 | @login_required
95 | def search_results(request, results=None):
96 | sqs = SearchQuerySet()
97 | if request.method=="POST":
98 | results = sqs.auto_query(request.POST.get('q')).filter_and(author=request.user)
99 | return render_to_response("search/search.html",
100 | {"results": results},
101 | context_instance=RequestContext(request))
102 |
103 | @login_required
104 | def new_url(request):
105 | form = UrlForm()
106 | item_type = "bookmark"
107 | if request.method == 'POST':
108 | form = UrlForm(request.POST, request.user)
109 | if form.is_valid():
110 | g = form.save(commit=False)
111 | g.user = request.user
112 | g.item_type = item_type
113 | g.save()
114 | form.save_m2m()
115 | messages.success(request, "Your bookmark was saved!")
116 | return redirect('yuk.views.profile', uname=request.user)
117 |
118 | return render_to_response('new_item.html', {'form':form},
119 | context_instance=RequestContext(request))
120 |
121 | @login_required
122 | def new_quote(request):
123 | form = QuoteForm()
124 | item_type = "quote"
125 | if request.method == 'POST':
126 | form = QuoteForm(request.POST, request.user)
127 | if form.is_valid():
128 | g = form.save(commit=False)
129 | g.user = request.user
130 | g.item_type = item_type
131 | g.save()
132 | if g.tags:
133 | form.save_m2m()
134 | messages.success(request, "Your quote was saved!")
135 | return redirect('yuk.views.profile', uname=request.user)
136 |
137 | return render_to_response('new_item.html', {'form':form},
138 | context_instance=RequestContext(request))
139 |
140 | @login_required
141 | def new_note(request):
142 | form = NoteForm()
143 | item_type = "note"
144 | if request.method == 'POST':
145 | form = NoteForm(request.POST, request.user)
146 | if form.is_valid():
147 | g = form.save(commit=False)
148 | g.user = request.user
149 | g.item_type = item_type
150 | g.save()
151 | form.save_m2m()
152 | messages.success(request, "Your note was saved!")
153 | return redirect('yuk.views.profile', uname=request.user)
154 | return render_to_response('new_item.html', {'form':form},
155 | context_instance=RequestContext(request))
156 |
157 |
158 | def remote_new_url(request):
159 | if not request.user.is_authenticated():
160 | return redirect('/bm_login/?next=%s' % request.get_full_path())
161 |
162 | init_data = {'url': request.GET.get('url', ' '),
163 | 'description': request.GET.get('description', ' '),
164 | 'displays': request.GET.get('title', ' ')}
165 | form = UrlForm(init_data)
166 |
167 | if request.method == 'POST':
168 | form = UrlForm(request.POST, request.user)
169 | if form.is_valid():
170 | g = form.save(commit=False)
171 | g.user = request.user
172 | g.date_created = datetime.datetime.now()
173 | g.item_type = "bookmark"
174 | g.save()
175 | if g.tags:
176 | form.save_m2m()
177 | return HttpResponse('''
178 | ''')
181 |
182 | return render_to_response('bookmarklet_add.html',
183 | {'form': form},
184 | context_instance=RequestContext(request))
185 |
186 |
187 | def bm_login(request):
188 | form = AuthenticationForm()
189 | redirect_to = "/add_bookmark_remote?&url=%s&description=%s&title=%s" % (
190 | request.GET.get('url'),
191 | request.GET.get('description'),
192 | request.GET.get('title'),
193 | )
194 |
195 | if request.method == 'POST':
196 | form = AuthenticationForm(data=request.POST)
197 | if form.is_valid():
198 | print >> sys.stderr, "1"
199 | auth_login(request, form.get_user())
200 |
201 | if request.session.test_cookie_worked():
202 | request.session.delete_test_cookie()
203 |
204 | return redirect(redirect_to)
205 | else:
206 | return render_to_response('bm_login.html',
207 | {'form': form},
208 | context_instance=RequestContext(request))
209 | else:
210 | form = AuthenticationForm(request)
211 |
212 | request.session.set_test_cookie()
213 | return render_to_response('bm_login.html',
214 | {'form': form, 'redir': redirect_to},
215 | context_instance=RequestContext(request))
216 |
217 | def tag_detail(request, uname, tag):
218 | tag = tag.replace('-',' ')
219 | results = get_list_or_404(Item, user=User.objects.get(username=uname),
220 | tags__name__in=[tag])
221 | return render_to_response('tag.html',
222 | {'results':results,
223 | 'tag':tag,
224 | 'uname':uname},
225 | context_instance=RequestContext(request))
226 |
227 | @login_required
228 | def edit_item(request, uname, item_id):
229 | item = get_object_or_404(Item, id=item_id, user=request.user)
230 |
231 | if item.item_type == "quote":
232 | itemform = QuoteForm
233 | elif item.item_type == "note":
234 | itemform = NoteForm
235 | else:
236 | itemform = UrlEditForm
237 |
238 | form = itemform(instance=item)
239 |
240 | if request.method=='POST':
241 | form = itemform(request.POST, request.user)
242 | attrs = ['displays', 'description', 'privacy_mode']
243 | if form.is_valid():
244 | for attr in attrs:
245 | setattr(item, attr, form.cleaned_data[attr])
246 | update_tags(item, form)
247 | item.save()
248 | return redirect('yuk.views.profile', uname=request.user.username)
249 |
250 | return render_to_response('edit_url.html',
251 | {'form':form},
252 | context_instance=RequestContext(request))
253 |
254 |
255 | @login_required
256 | def redir_to_profile(request, uname=None):
257 | return HttpResponseRedirect(request.user.get_absolute_url())
258 |
259 | def profile(request, uname):
260 | if request.user.is_authenticated() and uname == request.user.username:
261 | results = Item.objects.filter(user=request.user)
262 | else:
263 | results = get_list_or_404(Item, user=User.objects.get(username=uname),
264 | privacy_mode=False)
265 |
266 | return render_to_response('user_profile.html', {'results':results,
267 | 'uname':uname},
268 | context_instance=RequestContext(request))
269 |
270 | @login_required
271 | def del_item(request, uname, item_id):
272 | if request.method=='POST':
273 | item = Item.objects.get(id=request.POST['item_id'])
274 | item.delete()
275 | return HttpResponse("Deleted.")
276 | else:
277 | return redirect('yuk.views.profile', uname=request.user)
278 |
279 | @login_required
280 | def rss_import(request, uname):
281 | form = RssImportForm()
282 | if request.method == 'POST':
283 | form = RssImportForm(request.POST, request.user)
284 | if form.is_valid():
285 | feed = form.save(commit=False)
286 | feed.user = request.user
287 | feed.save()
288 | urls = rssdownload(request.user, feed.url)
289 | for i in urls['messages']:
290 | u = Url(url=i['url'], date_created=i['timestamp'],
291 | user=request.user, url_name=i['url_name'])
292 | u.save()
293 | return redirect('yuk.views.profile', uname=request.user)
294 | return render_to_response('rss_import.html', {'form':form},
295 | context_instance=RequestContext(request))
296 |
297 | def update_tags(item, form):
298 | itemset = set(item.tags.all())
299 | tagstringset = set(form.cleaned_data['tags'])
300 | for tag in itemset.difference(tagstringset):
301 | item.tags.remove(tag)
302 | for tag in tagstringset.difference(itemset):
303 | item.tags.add(tag)
304 | return item
305 |
306 | @login_required
307 | def export(request):
308 | return render_to_response("export.html",
309 | {"items":Item.objects.filter(user=request.user)},
310 | mimetype="text/plain")
311 |
312 | @login_required
313 | def import_text(request):
314 | form = BookmarkUploadForm()
315 | if request.method == 'POST':
316 | form = BookmarkUploadForm(request.POST, request.FILES)
317 | if form.is_valid():
318 | import_text_file(request)
319 | messages.success(request, "Your bookmarks have been imported"
320 | " successfully!")
321 | return redirect("yuk.views.profile", uname=request.user)
322 | else:
323 | messages.error(request, "Your upload failed. Please retry.")
324 | return render_to_response("bookmark_import.html", {"form":form},
325 | context_instance=RequestContext(request))
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
--------------------------------------------------------------------------------