├── .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 <DT> tag, and a description set off by <DD></DD> tag. 9 | 10 | (Example of this: 11 | 12 | <DT><A HREF="http://foo.com" TAGS="bar, baz, mu" PRIVATE="0">Foo</A> 13 | <DT><A HREF="http://bar.com" TAGS="tau, delta" PRIVATE="0">Bar</A> 14 | <DD>This is a description of a webpage about Bar.</DD>''' 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 <a> 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 <a> and <button>. 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 | <button type="submit" class="button positive"> 23 | <img src="css/blueprint/plugins/buttons/icons/tick.png" alt=""/> Save 24 | </button> 25 | 26 | <a class="button" href="/password/reset/"> 27 | <img src="css/blueprint/plugins/buttons/icons/key.png" alt=""/> Change Password 28 | </a> 29 | 30 | <a href="#" class="button negative"> 31 | <img src="css/blueprint/plugins/buttons/icons/cross.png" alt=""/> Cancel 32 | </a> 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 | <div id="401">You do not have permission to access this item! Sry m8</div> 4 | <div class="row"><label class="column1"></label><span class="column2"> 5 | <a class="main_return" 6 | href="{% url yuk.views.profile uname=user %}">Return to profile</a> 7 | </span></div> 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 | <h1 class="bye" align=center>Whoops!</h1> 5 | <div id='logout_message' align=center>We couldn't find what you were looking for. <a href="{{ the_url }}">Let's try again.</a></div> 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 | <h1 class="bye" align=center>Whoops!</h1> 5 | <div id='logout_message' align=center>Looks like something went wrong here. We'll look into it.<a href="{{ the_url }}">Let's try again.</a></div> 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 | <html> 10 | <head> 11 | <script type="text/javascript"> 12 | var username = "{{ user.username }}"; 13 | </script> 14 | <link href="http://fonts.googleapis.com/css?family=Permanent+Marker" 15 | rel='stylesheet' type='text/css'> 16 | <link rel="stylesheet" href="{{ blueprint_path }}screen.css" type="text/css" 17 | media="screen, projection"> 18 | <link rel="stylesheet" href="{{ blueprint_path }}print.css" type="text/css" 19 | media="print"> 20 | <!--[if lte IE 8]> 21 | <link rel="stylesheet" href="css/blueprint/ie.css" type="text/css" 22 | media="screen, projection"> 23 | <![endif]--> 24 | </head> 25 | <title>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 |
{% csrf_token %} 4 |

you must login first!

5 | {% for item in form %} 6 |
{{ item.errors }} {{ item }}
8 | {% endfor %} 9 |
10 |
11 | 12 |
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 |
7 | import bookmark file: 8 |
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 |
{% csrf_token %} 13 | {% for item in form %} 14 |
{{ item.errors }} {{ item }}
15 | {% endfor %} 16 | {% url yuk.views.profile uname=user as the_url %} 17 |
18 | 19 |
20 | {% endblock content %} 21 | -------------------------------------------------------------------------------- /yuk/templates/bookmarklet_add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 13 | 14 | Yuk! 15 | 16 |
17 | {% block content %} 18 |
19 | {% csrf_token %} 20 |

new bookmark:

21 | {% for item in form %} 22 | {% for error in item.errors %} 23 |
{{ error }}
24 | {% endfor %} 25 |
{{ item.label }}
26 |
{{ item }}
27 | {% endfor %} 28 | 30 |
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 |
7 | {% csrf_token %} 8 | {% for item in form %} 9 | {% if item.errors %} 10 | {% for error in item.errors %} 11 |
{{ error }}
12 | {% endfor %} 13 | {% endif %} 14 |

{{ item.label }}

15 |
{{ item }}
16 | {% endfor %} 17 | 18 | {% url yuk.views.profile uname=user.username as the_url %} 19 | 20 | Return to profile 21 | 22 |
23 |
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 |
  1. {{ i.url_name }}

    13 | {% else %} 14 |
  2. {{ i }}

    15 | {% endif %} 16 |

    (tags: {% for tag in i.tags %} {% url yuk.views.tag_detail tag=tag uname=user as tag_href %} {{ tag.name }} {% endfor %})

    17 |

    {{ i.url_desc }}

  3. 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 |

What's a yukmark?

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 |
25 | {% csrf_token %} 26 | 35 |
38 |
39 |
{% csrf_token %} 40 | 41 | Username: 42 | 43 | 44 | E-mail: 45 | 46 | 47 | 48 | Password: 49 | 50 | 51 | Password Again: 52 | 53 | 54 |
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 |
7 | {% csrf_token %} 8 | {% for item in form %} 9 | {% if item.errors %} 10 | {% for error in item.errors %} 11 |
{{ error }}
12 | {% endfor %} 13 | {% endif %} 14 | {% if item.html_name in text_areas %} 15 |

{{ item.label }}

16 |
{{ item }}
17 | {% else %} 18 |

{{ item.label }}

19 |
{{ item }}
20 | {% endif %} 21 | {% endfor %} 22 | 23 | {% url yuk.views.profile uname=user.username as the_url %} 24 | 26 | Return to profile 27 | 28 |
29 |
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 |
15 |
{% csrf_token %} 16 | {% for item in form %} 17 | {{ item.label }}{{ item }} 18 | {% endfor %} 19 | Submit
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 |
You've logged out. Log back in
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 |
4 | {% csrf_token %} 5 | {% if form.non_field_errors %} 6 | {% for error in form.non_field_errors %} 7 |
{{ error }}
8 | {% endfor %} 9 | {% endif %} 10 | {% for item in form %} 11 | {% if item.errors %} 12 | {% for error in item.errors %} 13 |
{{ error }}
14 | {% endfor %} 15 | {% endif %} 16 |

{{ item.label }}

17 |
{{ item }}
18 | {% endfor %} 19 |