├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── requirements.txt ├── resumator ├── __init__.py ├── admin.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── static │ └── resumator │ │ └── css │ │ ├── base.css │ │ ├── normalize.css │ │ ├── resumator-education.css │ │ ├── resumator-experience.css │ │ ├── resumator-projects.css │ │ ├── resumator-publications.css │ │ └── skeleton.css ├── templates │ ├── admin │ │ ├── base_site.html │ │ └── solo │ │ │ ├── change_form.html │ │ │ └── object_history.html │ └── resumator │ │ ├── base.html │ │ ├── education.html │ │ ├── experience.html │ │ ├── index.html │ │ ├── projects.html │ │ └── publications.html ├── templatetags │ ├── __init__.py │ ├── base_extras.py │ ├── experience_extras.py │ └── projects_extras.py ├── tests.py ├── urls.py ├── utils.py └── views.py ├── runtests.py ├── setup.py └── tests ├── __init__.py ├── models.py ├── test_models.py └── test_settings.py /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: http://goel.io/joe 2 | 3 | #####=== OSX ===##### 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear on external disk 15 | .Spotlight-V100 16 | .Trashes 17 | 18 | # Directories potentially created on remote AFP share 19 | .AppleDB 20 | .AppleDesktop 21 | Network Trash Folder 22 | Temporary Items 23 | .apdisk 24 | 25 | #### joe made this: http://goel.io/joe 26 | 27 | #####=== Python ===##### 28 | 29 | # Byte-compiled / optimized / DLL files 30 | __pycache__/ 31 | *.py[cod] 32 | 33 | # C extensions 34 | *.so 35 | 36 | # Distribution / packaging 37 | .Python 38 | env/ 39 | build/ 40 | develop-eggs/ 41 | dist/ 42 | downloads/ 43 | eggs/ 44 | lib/ 45 | lib64/ 46 | parts/ 47 | sdist/ 48 | var/ 49 | *3.egg-info/ 50 | .installed.cfg 51 | *.egg 52 | 53 | # PyInstaller 54 | # Usually these files are written by a python script from a template 55 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 56 | *.manifest 57 | *.spec 58 | 59 | # Installer logs 60 | pip-log.txt 61 | pip-delete-this-directory.txt 62 | 63 | # Unit test / coverage reports 64 | htmlcov/ 65 | .tox/ 66 | .coverage 67 | .cache 68 | nosetests.xml 69 | coverage.xml 70 | 71 | # Translations 72 | *.mo 73 | *.pot 74 | 75 | # Django stuff: 76 | *.log 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | 81 | # PyBuilder 82 | target/ 83 | 84 | # PyCharm 85 | .idea/ 86 | 87 | ##### DB ###### 88 | db.sqlite3 89 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | env: 5 | - DATABASE_ENGINE=sqlite 6 | - DATABASE_ENGINE=postgres 7 | - DATABASE_ENGINE=mysql 8 | install: 9 | - pip install -r requirements.txt 10 | - sh -c "if [ '$DATABASE_ENGINE' = 'postgres' ]; then pip install psycopg2; fi" 11 | - sh -c "if [ '$DATABASE_ENGINE' = 'mysql' ]; then pip install mysqlclient; mysql_tzinfo_to_sql 12 | /usr/share/zoneinfo | mysql -u root mysql; fi" 13 | script: python runtests.py 14 | deploy: 15 | provider: pypi 16 | user: ammsa 17 | password: 18 | secure: tJWgT0NyUPBKNPbZnyuc7AEn7aE1khdjWG1AiJ+W5Q7NQFme8WsDAYXBFJTTVZ+vA/AZlIYHumdd+q3yCm3cZe5Gu8paIn0pUq4FjOt1biSwHr/moe0ZTqsPVEonL5gRItAjcgW8t3I9mXN6PZeIsLPLpB8iVseVGqiITMoDGJn4A+3e9fqNmcLmKDlPCae9U/b1Kz4sN3ekdEpQVl5y5neqof+lZSwez2dF7m58Ubzr+m98JKfJqi0hx0JQpxerj3QHHTqdg3l8SJyZENI7tZMuwqySieGEAxSRDP5nti3HMN0YPdRbl5qat01mn3vgUkNg7KHRs6A5L1LZipDlwxz0efjYhfgrNNxME1X4Ksptqr3fauW2SSDZAXPpGSj8qCrf/acqldMAdc1i2gozBx34rAixu88fr7iByucQAUv+/163wq6ARdQxQc4i4sGK9kLj1iPQhfL5UrNyxz1KAsu5esxX4wNeGztywwUh4xYNZ7Z4NH2GX/CaA2EFxBofDnmzj3Bjx2DzU+Df4RBpxKEGVlm8fqtR+jAyNy3oD4ua9Q156o3AGon02QL+Jv2Wngbidfh8LiqRaKbgHtir+hpLU9EUyk03JGDukmq4iGod8iZa6jSPMHDOFtZs5WihTQRQTvEbdtf3Kou7V30eLHBQRqAvDcKATzRiJc/4ZPE= 19 | on: 20 | tags: true 21 | branch: master 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 Mustafa Abualsaud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | recursive-include resumator/static * 4 | recursive-include resumator/templates * 5 | recursive-include resumator/templatetags * 6 | recursive-include resumator/migrations * 7 | recursive-include docs * 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | django-resumator 3 | ===== 4 | |pypi| |travis| 5 | 6 | 7 | django-resumator is a lightwight app to create web-based resumes. Please visit `theresumator`_ for a django project that uses it. 8 | 9 | 10 | 11 | installation 12 | ----------- 13 | 14 | pip install django-resumator 15 | 16 | or 17 | 18 | python setup.py install 19 | 20 | Quick start 21 | ----------- 22 | 23 | 1. Add "resumator" to your INSTALLED_APPS setting like this:: 24 | 25 | INSTALLED_APPS = ( 26 | ... 27 | 'resumator', 28 | ) 29 | 30 | 2. Add the following to your setting.py:: 31 | 32 | MEDIA_ROOT = os.path.join(BASE_DIR, 'site_media') 33 | MEDIA_URL = '/media/' 34 | 35 | 2. Include the resumator URLconf in your project urls.py and add MEDIA URL to urlpatterns like this (Note: Don't do this in production):: 36 | 37 | from django.conf import settings 38 | from django.conf.urls.static import static 39 | 40 | urlpatterns = [ 41 | ... 42 | url(r'^resume/', include('resumator.urls')), 43 | ... 44 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 45 | 46 | 47 | 3. Run `python manage.py migrate` to create the resumator models. 48 | 49 | 4. Start the development server and visit http://127.0.0.1:8000/admin/ 50 | to modify your basic information model and edit your resume (you'll need the Admin app enabled). 51 | 52 | 5. Visit http://127.0.0.1:8000/resume/ to see your resume. 53 | 54 | 55 | Contributing 56 | ----------- 57 | 58 | 1. Fork it! 59 | 2. Create your feature branch: `git checkout -b my-new-feature` 60 | 3. Commit your changes: `git commit -am 'Add some feature'` 61 | 4. Push to the branch: `git push origin my-new-feature` 62 | 5. Submit a pull request :D 63 | 64 | License 65 | ----------- 66 | 67 | All parts of theresumator are free to use and abuse under the `open-source MIT license`_. 68 | 69 | .. |pypi| image:: https://badge.fury.io/py/django-resumator.svg 70 | :target: https://badge.fury.io/py/django-resumator 71 | .. |travis| image:: https://travis-ci.org/AmmsA/django-resumator.svg?branch=master 72 | :alt: Build Status - master branch 73 | :target: https://travis-ci.org/AmmsA/django-resumator 74 | .. _`theresumator`: https://github.com/AmmsA/theresumator 75 | .. _`open-source MIT license`: https://github.com/AmmsA/django-resumator/blob/master/LICENSE 76 | 77 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.8.6 2 | django-colorful==1.1.0 3 | django-html-field==0.1.3 4 | django-solo==1.1.0 5 | Pillow==3.0.0 6 | wheel==0.24.0 7 | -------------------------------------------------------------------------------- /resumator/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.1.6' 2 | -------------------------------------------------------------------------------- /resumator/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.db.utils import OperationalError 3 | from solo.admin import SingletonModelAdmin 4 | 5 | from .models import BasicInformation 6 | from .models import Project 7 | from .models import Experience 8 | from .models import Language 9 | from .models import Publication 10 | from .models import Education 11 | from .models import Settings 12 | 13 | 14 | admin.site.register(BasicInformation, SingletonModelAdmin) 15 | admin.site.register(Project) 16 | admin.site.register(Experience) 17 | admin.site.register(Language) 18 | admin.site.register(Publication) 19 | admin.site.register(Education) 20 | admin.site.register(Settings, SingletonModelAdmin) 21 | -------------------------------------------------------------------------------- /resumator/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | import colorful.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='BasicInformation', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('name', models.CharField(default=b'John Smith', max_length=25)), 19 | ('short_bio', models.CharField(default=b'My short bio', max_length=100, verbose_name='short bio', blank=True)), 20 | ('long_bio', models.TextField(default=b'My long bio', verbose_name='long bio', blank=True)), 21 | ('email', models.EmailField(default=b'email@example.com', max_length=254)), 22 | ('github', models.URLField(blank=True)), 23 | ('linkedin', models.URLField(blank=True)), 24 | ('image', models.ImageField(upload_to=b'media/images', blank=True)), 25 | ], 26 | options={ 27 | 'abstract': False, 28 | }, 29 | ), 30 | migrations.CreateModel( 31 | name='Education', 32 | fields=[ 33 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 34 | ('name', models.CharField(max_length=50, verbose_name='University name')), 35 | ('abbreviation', models.CharField(default=None, max_length=10, verbose_name='Degree abbreviation', blank=True)), 36 | ('start_date', models.DateField(null=True, verbose_name='start date', blank=True)), 37 | ('end_date', models.DateField(null=True, verbose_name='end date', blank=True)), 38 | ('major', models.CharField(default=None, max_length=50, blank=True)), 39 | ('gpa', models.CharField(default=None, max_length=10, verbose_name='GPA', blank=True)), 40 | ], 41 | options={ 42 | 'ordering': ['-end_date'], 43 | }, 44 | ), 45 | migrations.CreateModel( 46 | name='Experience', 47 | fields=[ 48 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 49 | ('company', models.CharField(max_length=50)), 50 | ('role', models.CharField(max_length=150)), 51 | ('start_date', models.DateField(null=True, verbose_name='start date', blank=True)), 52 | ('end_date', models.DateField(null=True, verbose_name='end date', blank=True)), 53 | ('description', models.TextField(default=None, verbose_name='description')), 54 | ('link', models.URLField(blank=True)), 55 | ('image', models.ImageField(upload_to=b'', blank=True)), 56 | ], 57 | options={ 58 | 'ordering': ['-end_date'], 59 | }, 60 | ), 61 | migrations.CreateModel( 62 | name='Language', 63 | fields=[ 64 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 65 | ('name', models.CharField(max_length=50)), 66 | ('experience', models.ManyToManyField(to='resumator.Experience', blank=True)), 67 | ], 68 | ), 69 | migrations.CreateModel( 70 | name='Project', 71 | fields=[ 72 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 73 | ('name', models.CharField(max_length=50)), 74 | ('description', models.TextField(default=None, verbose_name='description', blank=True)), 75 | ('start_date', models.DateField(null=True, verbose_name='start date', blank=True)), 76 | ('end_date', models.DateField(null=True, verbose_name='end date', blank=True)), 77 | ('link', models.URLField(blank=True)), 78 | ], 79 | ), 80 | migrations.CreateModel( 81 | name='Publication', 82 | fields=[ 83 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 84 | ('title', models.CharField(max_length=100)), 85 | ('authors', models.CharField(max_length=200, blank=True)), 86 | ('conference', models.CharField(max_length=200, blank=True)), 87 | ('abstract', models.TextField(blank=True)), 88 | ('year', models.CharField(max_length=4, blank=True)), 89 | ('link', models.URLField(blank=True)), 90 | ], 91 | options={ 92 | 'ordering': ['-year'], 93 | }, 94 | ), 95 | migrations.CreateModel( 96 | name='Settings', 97 | fields=[ 98 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 99 | ('base_color', colorful.fields.RGBColorField(default=b'bc0000', verbose_name='Base color')), 100 | ], 101 | options={ 102 | 'abstract': False, 103 | }, 104 | ), 105 | migrations.AddField( 106 | model_name='language', 107 | name='projects', 108 | field=models.ManyToManyField(to='resumator.Project', blank=True), 109 | ), 110 | ] 111 | -------------------------------------------------------------------------------- /resumator/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ammsa/django-resumator/0939efe30abbd679fd3fdcd9903f3b7be80b87d8/resumator/migrations/__init__.py -------------------------------------------------------------------------------- /resumator/models.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ObjectDoesNotExist, ValidationError 2 | from django.db import models 3 | from django.utils.translation import ugettext_lazy as _ 4 | 5 | from solo.models import SingletonModel 6 | from colorful.fields import RGBColorField 7 | 8 | 9 | class BasicInformation(SingletonModel): 10 | name = models.CharField(max_length=25, 11 | default="John Smith") 12 | short_bio = models.CharField(max_length=100, 13 | blank=True, 14 | verbose_name=_("short bio"), 15 | default="My short bio") 16 | long_bio = models.TextField(blank=True, 17 | verbose_name=_("long bio"), 18 | default="My long bio") 19 | email = models.EmailField(default="email@example.com") 20 | github = models.URLField(blank=True) 21 | linkedin = models.URLField(blank=True) 22 | image = models.ImageField(upload_to="images", blank=True) 23 | 24 | def __repr__(self): 25 | return '' % self.name 26 | 27 | def __str__(self): 28 | return self.name.title() 29 | 30 | 31 | class Education(models.Model): 32 | name = models.CharField(max_length=50, 33 | verbose_name=_("University name")) 34 | abbreviation = models.CharField(max_length=10, 35 | blank=True, 36 | default=None, 37 | verbose_name=_("Degree abbreviation")) 38 | start_date = models.DateField(null=True, blank=True, 39 | verbose_name=_("start date")) 40 | end_date = models.DateField(null=True, 41 | blank=True, 42 | verbose_name=_("end date")) 43 | major = models.CharField(max_length=50, 44 | blank=True, 45 | default=None) 46 | gpa = models.CharField(max_length=10, 47 | blank=True, 48 | default=None, 49 | verbose_name=_("GPA")) 50 | 51 | def __repr__(self): 52 | return '' % self.name 53 | 54 | def __str__(self): 55 | return '%s in %s' % (self.abbreviation, self.major) 56 | 57 | def save(self, *args, **kwargs): 58 | self.clean() 59 | return super(self.__class__, self).save(*args, **kwargs) 60 | 61 | def clean(self): 62 | if self.start_date and self.end_date: 63 | if self.start_date > self.end_date: 64 | raise ValidationError({"start_date": _("Start date must be " 65 | "before end date."), 66 | "end_date": _("Start date must be " 67 | "before end date.")}) 68 | 69 | class Meta: 70 | ordering = ['-end_date'] 71 | 72 | 73 | class Publication(models.Model): 74 | title = models.CharField(max_length=100) 75 | authors = models.CharField(max_length=200, 76 | blank=True) 77 | conference = models.CharField(max_length=200, 78 | blank=True) 79 | abstract = models.TextField(blank=True) 80 | year = models.CharField(max_length=4, 81 | blank=True) 82 | link = models.URLField(blank=True) 83 | 84 | def __repr__(self): 85 | return '' % self.name 86 | 87 | def __str__(self): 88 | return self.title 89 | 90 | class Meta: 91 | ordering = ['-year'] 92 | 93 | 94 | class Project(models.Model): 95 | name = models.CharField(max_length=50) 96 | description = models.TextField(default=None, 97 | blank=True, 98 | verbose_name=_("description")) 99 | start_date = models.DateField(null=True, 100 | blank=True, 101 | verbose_name=_("start date")) 102 | end_date = models.DateField(null=True, 103 | blank=True, 104 | verbose_name=_("end date")) 105 | link = models.URLField(blank=True) 106 | 107 | def __repr__(self): 108 | return '' % self.name 109 | 110 | def __str__(self): 111 | return self.name 112 | 113 | def save(self, *args, **kwargs): 114 | self.clean() 115 | return super(self.__class__, self).save(*args, **kwargs) 116 | 117 | def clean(self): 118 | if self.start_date and self.end_date: 119 | if self.start_date > self.end_date: 120 | raise ValidationError({"start_date": _("Start date must be " 121 | "before end date."), 122 | "end_date": _("Start date must be " 123 | "before end date.")}) 124 | 125 | def get_languages(self): 126 | languages = Language.objects.all() 127 | used_languages = [] 128 | for language in languages: 129 | try: 130 | language.projects.get(pk=self.pk) 131 | used_languages.append(language) 132 | except ObjectDoesNotExist: 133 | pass 134 | return {self: used_languages} 135 | 136 | 137 | class Experience(models.Model): 138 | company = models.CharField(max_length=50) 139 | role = models.CharField(max_length=150) 140 | start_date = models.DateField(null=True, 141 | blank=True, 142 | verbose_name=_("start date")) 143 | end_date = models.DateField(null=True, 144 | blank=True, 145 | verbose_name=_("end date")) 146 | description = models.TextField(default=None, 147 | verbose_name=_("description")) 148 | link = models.URLField(blank=True) 149 | image = models.ImageField(upload_to="images", 150 | blank=True) 151 | 152 | def __repr__(self): 153 | return '' % self.company 154 | 155 | def __str__(self): 156 | return '%s at %s' % (self.role.capitalize(), self.company) 157 | 158 | class Meta: 159 | ordering = ['-end_date'] 160 | 161 | def save(self, *args, **kwargs): 162 | self.clean() 163 | return super(self.__class__, self).save(*args, **kwargs) 164 | 165 | def clean(self): 166 | if self.start_date and self.end_date: 167 | if self.start_date > self.end_date: 168 | raise ValidationError({"start_date": _("Start date must be " 169 | "before end date."), 170 | "end_date": _("Start date must be " 171 | "before end date.")}) 172 | 173 | def get_languages(self): 174 | languages = Language.objects.all() 175 | used_languages = [] 176 | for language in languages: 177 | try: 178 | language.experience.get(pk=self.pk) 179 | used_languages.append(language.name) 180 | except ObjectDoesNotExist: 181 | pass 182 | return {self: used_languages} 183 | 184 | 185 | class Language(models.Model): 186 | name = models.CharField(max_length=50) 187 | experience = models.ManyToManyField(Experience, 188 | blank=True) 189 | projects = models.ManyToManyField(Project, 190 | blank=True) 191 | 192 | def __repr__(self): 193 | return '' % self.name 194 | 195 | def __str__(self): 196 | return self.name 197 | 198 | 199 | class Settings(SingletonModel): 200 | base_color = RGBColorField(blank=False, 201 | verbose_name=_("Base color"), 202 | default="bc0000") 203 | 204 | def __repr__(self): 205 | return '' % self.base_color 206 | 207 | def __str__(self): 208 | return 'Settings Singleton' 209 | -------------------------------------------------------------------------------- /resumator/static/resumator/css/base.css: -------------------------------------------------------------------------------- 1 | b{ 2 | font-weight:700; 3 | } 4 | /* turn on unselectable */ 5 | div{ 6 | 7 | } 8 | 9 | ::selection { 10 | background: #e6e6e6; /* WebKit/Blink Browsers */ 11 | } 12 | ::-moz-selection { 13 | background: #e6e6e6; /* Gecko Browsers */ 14 | } 15 | .side-bar { 16 | 17 | } 18 | .side-bar-bio { 19 | text-align: left; 20 | text-transform: uppercase; 21 | font-size: 13px; 22 | margin-bottom: 25px; 23 | } 24 | .side-bar-icon{ 25 | color: #555555; 26 | } 27 | .side-bar-items{ 28 | -webkit-user-select: none; /* Chrome/Safari */ 29 | -moz-user-select: none; /* Firefox */ 30 | -ms-user-select: none; /* IE10+ */ 31 | 32 | /* Rules below not implemented in browsers yet */ 33 | -o-user-select: none; 34 | user-select: none; 35 | } 36 | .side-bar-item { 37 | text-transform: uppercase; 38 | letter-spacing: .1rem; 39 | margin-bottom: 10px; 40 | } 41 | .side-bar-links a, 42 | .side-bar-items a{ 43 | color: #555555; 44 | } 45 | .side-bar-links a:hover, 46 | .side-bar-items a:hover{ 47 | color: #000000; 48 | } 49 | .side-bar-item.hvr-grow{ 50 | display: block; 51 | } 52 | .side-bar-links { 53 | margin-top: 100px; 54 | } 55 | .whole-content { 56 | margin-top: 10%; 57 | margin-bottom: 10%; 58 | } 59 | .content{ 60 | margin-top: 5%; 61 | } 62 | .side-line{ 63 | border-right: thick solid #ef4a60; 64 | } 65 | .img-circle { 66 | border-radius: 50%; 67 | } 68 | .thumbnail-container { 69 | width: 120px; 70 | min-height: 120px; 71 | max-height: inherit; 72 | border-radius: 50%; 73 | } 74 | .center{ 75 | text-align: center; 76 | } 77 | .right-text{ 78 | text-align: right; 79 | } 80 | .tag{ 81 | display: inline-block; 82 | height: 20px; 83 | padding: 0 7px; 84 | color: #555; 85 | text-align: center; 86 | font-size: 8px; 87 | font-weight: 700; 88 | line-height: 20px; 89 | letter-spacing: .1rem; 90 | text-transform: uppercase; 91 | text-decoration: none; 92 | white-space: nowrap; 93 | background-color: transparent; 94 | border-radius: 4px; 95 | border: 1px solid #bbb; 96 | box-sizing: border-box; 97 | margin: 0 2px; 98 | } 99 | 100 | @media only screen and (max-width: 550px) { 101 | .side-bar-icon { 102 | display: none; 103 | } 104 | .side-line { 105 | border-right: none; !important; 106 | border-bottom: thick solid #ef4a60; 107 | padding-bottom: 30px; 108 | margin-bottom: 50px; 109 | } 110 | 111 | .side-bar-links { 112 | margin-top: 78px; 113 | } 114 | 115 | .thumbnail { 116 | text-align: center; 117 | } 118 | 119 | .side-bar-items a { 120 | text-align: center; 121 | } 122 | 123 | .side-bar-bio{ 124 | text-align: center; 125 | } 126 | } 127 | 128 | .resumator-box-info { 129 | position:absolute; 130 | right:10px; 131 | font-size: 10px; 132 | color: #999; 133 | } 134 | 135 | .resumator-box-info a, 136 | .resumator-box-info a:hover { 137 | color: #999; 138 | } -------------------------------------------------------------------------------- /resumator/static/resumator/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } -------------------------------------------------------------------------------- /resumator/static/resumator/css/resumator-education.css: -------------------------------------------------------------------------------- 1 | .resumator-education{ 2 | margin-top: 10px; 3 | margin-bottom: 20px; 4 | color: #555555; 5 | } 6 | .resumator-education a, 7 | .resumator-education a{ 8 | color: #555555; 9 | } 10 | .resumator-education a:hover, 11 | .resumator-education a:hover{ 12 | color: #000000; 13 | } 14 | .resumator-education .authors, 15 | .resumator-education .conference, 16 | .resumator-education p{ 17 | font-size: 14px; 18 | } 19 | .resumator-education p{ 20 | font-size: 14px; 21 | } 22 | .resumator-education .description{ 23 | min-height: 50px; 24 | border-bottom: thin solid #eeeeee; 25 | } 26 | .resumator-education .date, 27 | .resumator-education .number{ 28 | font-size: 12px; 29 | font-family: 'Open Sans', sans-serif; 30 | } 31 | .resumator-education .limit{ 32 | overflow:hidden; 33 | text-overflow:ellipsis; 34 | } 35 | @media only screen and (max-width: 550px) { 36 | .resumator-education .date, 37 | .resumator-education .number{ 38 | text-align: left; 39 | } 40 | } -------------------------------------------------------------------------------- /resumator/static/resumator/css/resumator-experience.css: -------------------------------------------------------------------------------- 1 | .resumator-experience { 2 | margin-top: 10px; 3 | margin-bottom: 20px; 4 | color: #555555; 5 | } 6 | .resumator-experience a, 7 | .resumator-experience a{ 8 | color: #555555; 9 | } 10 | .resumator-experience a:hover, 11 | .resumator-experience a:hover{ 12 | color: #000000; 13 | } 14 | .resumator-experience p{ 15 | font-size: 14px; 16 | } 17 | .resumator-experience .description{ 18 | min-height: 120px; 19 | border-bottom: thin solid #eeeeee; 20 | } 21 | .resumator-experience .date{ 22 | font-size: 12px; 23 | font-family: 'Open Sans', sans-serif; 24 | } 25 | .resumator-experience .limit{ 26 | overflow:hidden; 27 | text-overflow:ellipsis; 28 | } 29 | .resumator-experience .image{ 30 | height:inherit; 31 | } 32 | .resumator-experience img{ 33 | vertical-align: middle; 34 | width: 60px; 35 | height: 60px; 36 | } 37 | .resumator-experience .tags{ 38 | margin-bottom: 10px; 39 | } 40 | @media only screen and (max-width: 550px) { 41 | .resumator-experience .date{ 42 | text-align: left; 43 | } 44 | } -------------------------------------------------------------------------------- /resumator/static/resumator/css/resumator-projects.css: -------------------------------------------------------------------------------- 1 | .resumator-project{ 2 | margin-top: 10px; 3 | margin-bottom: 20px; 4 | color: #555555; 5 | } 6 | .resumator-project a, 7 | .resumator-project a{ 8 | color: #555555; 9 | } 10 | .resumator-project a:hover, 11 | .resumator-project a:hover{ 12 | color: #000000; 13 | } 14 | .resumator-project p{ 15 | font-size: 14px; 16 | } 17 | .resumator-project .description{ 18 | min-height: 120px; 19 | border-bottom: thin solid #eeeeee; 20 | } 21 | .resumator-project .date{ 22 | font-size: 12px; 23 | font-family: 'Open Sans', sans-serif; 24 | } 25 | .resumator-project .limit{ 26 | overflow:hidden; 27 | text-overflow:ellipsis; 28 | } 29 | .resumator-project .tags{ 30 | margin-bottom: 10px; 31 | } 32 | @media only screen and (max-width: 550px) { 33 | .resumator-project .date{ 34 | text-align: left; 35 | } 36 | } -------------------------------------------------------------------------------- /resumator/static/resumator/css/resumator-publications.css: -------------------------------------------------------------------------------- 1 | .resumator-publication{ 2 | margin-top: 10px; 3 | margin-bottom: 20px; 4 | color: #555555; 5 | } 6 | .resumator-publication a, 7 | .resumator-publication a{ 8 | color: #555555; 9 | } 10 | .resumator-publication a:hover, 11 | .resumator-publication a:hover{ 12 | color: #000000; 13 | } 14 | .resumator-publication .authors, 15 | .resumator-publication .conference, 16 | .resumator-publication p{ 17 | font-size: 14px; 18 | } 19 | .resumator-publication p{ 20 | font-size: 14px; 21 | } 22 | .resumator-publication .description{ 23 | min-height: 120px; 24 | border-bottom: thin solid #eeeeee; 25 | } 26 | .resumator-publication .date{ 27 | font-size: 12px; 28 | font-family: 'Open Sans', sans-serif; 29 | } 30 | .resumator-publication .limit{ 31 | overflow:hidden; 32 | text-overflow:ellipsis; 33 | } 34 | @media only screen and (max-width: 550px) { 35 | .resumator-publication .date{ 36 | text-align: left; 37 | } 38 | } -------------------------------------------------------------------------------- /resumator/static/resumator/css/skeleton.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Skeleton V2.0.4 3 | * Copyright 2014, Dave Gamache 4 | * www.getskeleton.com 5 | * Free to use under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 12/29/2014 8 | */ 9 | 10 | 11 | /* Table of contents 12 | –––––––––––––––––––––––––––––––––––––––––––––––––– 13 | - Grid 14 | - Base Styles 15 | - Typography 16 | - Links 17 | - Buttons 18 | - Forms 19 | - Lists 20 | - Code 21 | - Tables 22 | - Spacing 23 | - Utilities 24 | - Clearing 25 | - Media Queries 26 | */ 27 | 28 | 29 | /* Grid 30 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 31 | .container { 32 | position: relative; 33 | width: 100%; 34 | max-width: 960px; 35 | margin: 0 auto; 36 | padding: 0 20px; 37 | box-sizing: border-box; } 38 | .column, 39 | .columns { 40 | width: 100%; 41 | float: left; 42 | box-sizing: border-box; } 43 | 44 | /* For devices larger than 400px */ 45 | @media (min-width: 400px) { 46 | .container { 47 | width: 85%; 48 | padding: 0; } 49 | } 50 | 51 | /* For devices larger than 550px */ 52 | @media (min-width: 550px) { 53 | .container { 54 | width: 80%; } 55 | .column, 56 | .columns { 57 | margin-left: 4%; } 58 | .column:first-child, 59 | .columns:first-child { 60 | margin-left: 0; } 61 | 62 | .one.column, 63 | .one.columns { width: 4.66666666667%; } 64 | .two.columns { width: 13.3333333333%; } 65 | .three.columns { width: 22%; } 66 | .four.columns { width: 30.6666666667%; } 67 | .five.columns { width: 39.3333333333%; } 68 | .six.columns { width: 48%; } 69 | .seven.columns { width: 56.6666666667%; } 70 | .eight.columns { width: 65.3333333333%; } 71 | .nine.columns { width: 74.0%; } 72 | .ten.columns { width: 82.6666666667%; } 73 | .eleven.columns { width: 91.3333333333%; } 74 | .twelve.columns { width: 100%; margin-left: 0; } 75 | 76 | .one-third.column { width: 30.6666666667%; } 77 | .two-thirds.column { width: 65.3333333333%; } 78 | 79 | .one-half.column { width: 48%; } 80 | 81 | /* Offsets */ 82 | .offset-by-one.column, 83 | .offset-by-one.columns { margin-left: 8.66666666667%; } 84 | .offset-by-two.column, 85 | .offset-by-two.columns { margin-left: 17.3333333333%; } 86 | .offset-by-three.column, 87 | .offset-by-three.columns { margin-left: 26%; } 88 | .offset-by-four.column, 89 | .offset-by-four.columns { margin-left: 34.6666666667%; } 90 | .offset-by-five.column, 91 | .offset-by-five.columns { margin-left: 43.3333333333%; } 92 | .offset-by-six.column, 93 | .offset-by-six.columns { margin-left: 52%; } 94 | .offset-by-seven.column, 95 | .offset-by-seven.columns { margin-left: 60.6666666667%; } 96 | .offset-by-eight.column, 97 | .offset-by-eight.columns { margin-left: 69.3333333333%; } 98 | .offset-by-nine.column, 99 | .offset-by-nine.columns { margin-left: 78.0%; } 100 | .offset-by-ten.column, 101 | .offset-by-ten.columns { margin-left: 86.6666666667%; } 102 | .offset-by-eleven.column, 103 | .offset-by-eleven.columns { margin-left: 95.3333333333%; } 104 | 105 | .offset-by-one-third.column, 106 | .offset-by-one-third.columns { margin-left: 34.6666666667%; } 107 | .offset-by-two-thirds.column, 108 | .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } 109 | 110 | .offset-by-one-half.column, 111 | .offset-by-one-half.columns { margin-left: 52%; } 112 | 113 | } 114 | 115 | 116 | /* Base Styles 117 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 118 | /* NOTE 119 | html is set to 62.5% so that all the REM measurements throughout Skeleton 120 | are based on 10px sizing. So basically 1.5rem = 15px :) */ 121 | html { 122 | font-size: 62.5%; } 123 | body { 124 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ 125 | line-height: 1.6; 126 | font-weight: 400; 127 | font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; 128 | color: #222; } 129 | 130 | 131 | /* Typography 132 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 133 | h1, h2, h3, h4, h5, h6 { 134 | margin-top: 0; 135 | margin-bottom: 2rem; 136 | font-weight: 300; } 137 | h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} 138 | h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } 139 | h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } 140 | h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } 141 | h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } 142 | h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } 143 | 144 | /* Larger than phablet */ 145 | @media (min-width: 550px) { 146 | h1 { font-size: 5.0rem; } 147 | h2 { font-size: 4.2rem; } 148 | h3 { font-size: 3.6rem; } 149 | h4 { font-size: 3.0rem; } 150 | h5 { font-size: 2.4rem; } 151 | h6 { font-size: 1.5rem; } 152 | } 153 | 154 | p { 155 | margin-top: 0; } 156 | 157 | 158 | /* Links 159 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 160 | a { 161 | color: #1EAEDB; } 162 | a:hover { 163 | color: #0FA0CE; } 164 | 165 | 166 | /* Buttons 167 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 168 | .button, 169 | button, 170 | input[type="submit"], 171 | input[type="reset"], 172 | input[type="button"] { 173 | display: inline-block; 174 | height: 38px; 175 | padding: 0 30px; 176 | color: #555; 177 | text-align: center; 178 | font-size: 11px; 179 | font-weight: 600; 180 | line-height: 38px; 181 | letter-spacing: .1rem; 182 | text-transform: uppercase; 183 | text-decoration: none; 184 | white-space: nowrap; 185 | background-color: transparent; 186 | border-radius: 4px; 187 | border: 1px solid #bbb; 188 | cursor: pointer; 189 | box-sizing: border-box; } 190 | .button:hover, 191 | button:hover, 192 | input[type="submit"]:hover, 193 | input[type="reset"]:hover, 194 | input[type="button"]:hover, 195 | .button:focus, 196 | button:focus, 197 | input[type="submit"]:focus, 198 | input[type="reset"]:focus, 199 | input[type="button"]:focus { 200 | color: #333; 201 | border-color: #888; 202 | outline: 0; } 203 | .button.button-primary, 204 | button.button-primary, 205 | input[type="submit"].button-primary, 206 | input[type="reset"].button-primary, 207 | input[type="button"].button-primary { 208 | color: #FFF; 209 | background-color: #33C3F0; 210 | border-color: #33C3F0; } 211 | .button.button-primary:hover, 212 | button.button-primary:hover, 213 | input[type="submit"].button-primary:hover, 214 | input[type="reset"].button-primary:hover, 215 | input[type="button"].button-primary:hover, 216 | .button.button-primary:focus, 217 | button.button-primary:focus, 218 | input[type="submit"].button-primary:focus, 219 | input[type="reset"].button-primary:focus, 220 | input[type="button"].button-primary:focus { 221 | color: #FFF; 222 | background-color: #1EAEDB; 223 | border-color: #1EAEDB; } 224 | 225 | 226 | /* Forms 227 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 228 | input[type="email"], 229 | input[type="number"], 230 | input[type="search"], 231 | input[type="text"], 232 | input[type="tel"], 233 | input[type="url"], 234 | input[type="password"], 235 | textarea, 236 | select { 237 | height: 38px; 238 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ 239 | background-color: #fff; 240 | border: 1px solid #D1D1D1; 241 | border-radius: 4px; 242 | box-shadow: none; 243 | box-sizing: border-box; } 244 | /* Removes awkward default styles on some inputs for iOS */ 245 | input[type="email"], 246 | input[type="number"], 247 | input[type="search"], 248 | input[type="text"], 249 | input[type="tel"], 250 | input[type="url"], 251 | input[type="password"], 252 | textarea { 253 | -webkit-appearance: none; 254 | -moz-appearance: none; 255 | appearance: none; } 256 | textarea { 257 | min-height: 65px; 258 | padding-top: 6px; 259 | padding-bottom: 6px; } 260 | input[type="email"]:focus, 261 | input[type="number"]:focus, 262 | input[type="search"]:focus, 263 | input[type="text"]:focus, 264 | input[type="tel"]:focus, 265 | input[type="url"]:focus, 266 | input[type="password"]:focus, 267 | textarea:focus, 268 | select:focus { 269 | border: 1px solid #33C3F0; 270 | outline: 0; } 271 | label, 272 | legend { 273 | display: block; 274 | margin-bottom: .5rem; 275 | font-weight: 600; } 276 | fieldset { 277 | padding: 0; 278 | border-width: 0; } 279 | input[type="checkbox"], 280 | input[type="radio"] { 281 | display: inline; } 282 | label > .label-body { 283 | display: inline-block; 284 | margin-left: .5rem; 285 | font-weight: normal; } 286 | 287 | 288 | /* Lists 289 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 290 | ul { 291 | list-style: circle inside; } 292 | ol { 293 | list-style: decimal inside; } 294 | ol, ul { 295 | padding-left: 0; 296 | margin-top: 0; } 297 | ul ul, 298 | ul ol, 299 | ol ol, 300 | ol ul { 301 | margin: 1.5rem 0 1.5rem 3rem; 302 | font-size: 90%; } 303 | li { 304 | margin-bottom: 1rem; } 305 | 306 | 307 | /* Code 308 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 309 | code { 310 | padding: .2rem .5rem; 311 | margin: 0 .2rem; 312 | font-size: 90%; 313 | white-space: nowrap; 314 | background: #F1F1F1; 315 | border: 1px solid #E1E1E1; 316 | border-radius: 4px; } 317 | pre > code { 318 | display: block; 319 | padding: 1rem 1.5rem; 320 | white-space: pre; } 321 | 322 | 323 | /* Tables 324 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 325 | th, 326 | td { 327 | padding: 12px 15px; 328 | text-align: left; 329 | border-bottom: 1px solid #E1E1E1; } 330 | th:first-child, 331 | td:first-child { 332 | padding-left: 0; } 333 | th:last-child, 334 | td:last-child { 335 | padding-right: 0; } 336 | 337 | 338 | /* Spacing 339 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 340 | button, 341 | .button { 342 | margin-bottom: 1rem; } 343 | input, 344 | textarea, 345 | select, 346 | fieldset { 347 | margin-bottom: 1.5rem; } 348 | pre, 349 | blockquote, 350 | dl, 351 | figure, 352 | table, 353 | p, 354 | ul, 355 | ol, 356 | form { 357 | margin-bottom: 2.5rem; } 358 | 359 | 360 | /* Utilities 361 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 362 | .u-full-width { 363 | width: 100%; 364 | box-sizing: border-box; } 365 | .u-max-full-width { 366 | max-width: 100%; 367 | box-sizing: border-box; } 368 | .u-pull-right { 369 | float: right; } 370 | .u-pull-left { 371 | float: left; } 372 | 373 | 374 | /* Misc 375 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 376 | hr { 377 | margin-top: 3rem; 378 | margin-bottom: 3.5rem; 379 | border-width: 0; 380 | border-top: 1px solid #E1E1E1; } 381 | 382 | 383 | /* Clearing 384 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 385 | 386 | /* Self Clearing Goodness */ 387 | .container:after, 388 | .row:after, 389 | .u-cf { 390 | content: ""; 391 | display: table; 392 | clear: both; } 393 | 394 | 395 | /* Media Queries 396 | –––––––––––––––––––––––––––––––––––––––––––––––––– */ 397 | /* 398 | Note: The best way to structure the use of media queries is to create the queries 399 | near the relevant code. For example, if you wanted to change the styles for buttons 400 | on small devices, paste the mobile query code up in the buttons section and style it 401 | there. 402 | */ 403 | 404 | 405 | /* Larger than mobile */ 406 | @media (min-width: 400px) {} 407 | 408 | /* Larger than phablet (also point when grid becomes active) */ 409 | @media (min-width: 550px) {} 410 | 411 | /* Larger than tablet */ 412 | @media (min-width: 750px) {} 413 | 414 | /* Larger than desktop */ 415 | @media (min-width: 1000px) {} 416 | 417 | /* Larger than Desktop HD */ 418 | @media (min-width: 1200px) {} 419 | -------------------------------------------------------------------------------- /resumator/templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | 3 | {% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} 4 | 5 | {% block branding %} 6 |

My Resumator Administration

7 | {% endblock %} 8 | 9 | {% block nav-global %}{% endblock %} 10 | -------------------------------------------------------------------------------- /resumator/templates/admin/solo/change_form.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/change_form.html" %} 2 | {% load i18n %} 3 | {% load admin_urls %} 4 | {% load url from future %} 5 | 6 | {% block breadcrumbs %} 7 | 12 | {% endblock %} 13 | 14 | {% block object-tools-items %} 15 |
  • {% trans "History" %}
  • 16 | {% if has_absolute_url %}
  • {% trans "View on site" %}
  • {% endif%} 17 | {% endblock %} -------------------------------------------------------------------------------- /resumator/templates/admin/solo/object_history.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/object_history.html" %} 2 | {% load i18n %} 3 | {% load url from future %} 4 | 5 | {% block breadcrumbs %} 6 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /resumator/templates/resumator/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% load i18n %}{% load staticfiles %}{% load base_extras %} 3 | 8 | {% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} 9 | 10 | 11 | 13 | 14 | {% block title %}{{ resumator_basicinformation.name }}{% endblock %} 15 | 17 | 18 | 19 | 21 | 22 | 23 | 25 | 26 | {% block extrafonts %}{% endblock %} 27 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | {% if resumator_settings %} 36 | 47 | {% endif %} 48 | {% block extrastyles %}{% endblock %} 49 | 50 | 52 | 53 | {% block blockbots %} 54 | 55 | {% endblock %} 56 | {% block extrahead %}{% endblock %} 57 | 58 | 59 | 60 | 62 |
    63 |
    64 | 154 |
    155 | {% block extracontent %} 156 |
    Main content
    157 |

    158 | This is where the main content of the website goes. If you need some help, please go to 159 | resumator github page. 160 |

    161 | {% endblock %} 162 |
    163 |
    164 |
    165 |

    Created using theresumator.

    166 | 168 | {% block extrabody %}{% endblock %} 169 | 170 | 171 | -------------------------------------------------------------------------------- /resumator/templates/resumator/education.html: -------------------------------------------------------------------------------- 1 | {% extends "resumator/base.html" %} 2 | {% load staticfiles %} 3 | {% block extrastyles %}{% endblock %} 4 | {% block extrafonts %}{% endblock %} 5 | 6 | {% block title %}{{ bas }}{% endblock %} 7 | 8 | {% block extracontent %} 9 |
    Education
    10 | {% for education in resumator_education %} 11 |
    12 |
    13 |
    14 |
    {{ education.name }}
    15 |
    16 | {% if education.start_date and not education.end_date%}{{ education.start_date }} - Today 17 | {% elif education.start_date and education.end_date%}{{ education.start_date }} - {{ education.end_date }} 18 | {% elif education.end_date and not education.start_date%}{{ education.end_date }} 19 | {% endif %} 20 |
    21 |
    22 |
    23 |
    {{ education }}
    24 | {% if education.gpa %} 25 |
    GPA: {{ education.gpa }}
    26 | {% endif %} 27 |
    28 |
    29 |
    30 | {% endfor %} 31 | {% endblock %} -------------------------------------------------------------------------------- /resumator/templates/resumator/experience.html: -------------------------------------------------------------------------------- 1 | {% extends "resumator/base.html" %} 2 | {% load experience_extras %} 3 | {% load staticfiles %} 4 | {% block extrastyles %}{% endblock %} 5 | {% block extrafonts %}{% endblock %} 6 | 7 | {% block title %}{{ resumator_basicinformation.name }} - Experience{% endblock %} 8 | 9 | {% block extracontent %} 10 |
    Experience
    11 | {% for experience in resumator_experience %} 12 | 46 | {% endfor %} 47 | {% endblock %} -------------------------------------------------------------------------------- /resumator/templates/resumator/index.html: -------------------------------------------------------------------------------- 1 | {% extends "resumator/base.html" %} 2 | 3 | {% block title %}{{ resumator_basicinformation.name }} - About{% endblock %} 4 | 5 | {% block extracontent %} 6 |
    About {{ resumator_basicinformation.name }}
    7 | {% spaceless %}{{ resumator_basicinformation.long_bio | safe}}{% endspaceless %} 8 | {% endblock %} -------------------------------------------------------------------------------- /resumator/templates/resumator/projects.html: -------------------------------------------------------------------------------- 1 | {% extends "resumator/base.html" %} 2 | {% load projects_extras %} 3 | {% load staticfiles %} 4 | {% block extrastyles %}{% endblock %} 5 | {% block extrafonts %}{% endblock %} 6 | 7 | {% block title %}{{ resumator_basicinformation.name }} - Projects{% endblock %} 8 | 9 | {% block extracontent %} 10 |
    Projects
    11 | {% for project in resumator_project %} 12 | 41 | {% endfor %} 42 | {% endblock %} -------------------------------------------------------------------------------- /resumator/templates/resumator/publications.html: -------------------------------------------------------------------------------- 1 | {% extends "resumator/base.html" %} 2 | {% load staticfiles %} 3 | {% block extrastyles %}{% endblock %} 4 | {% block extrafonts %}{% endblock %} 5 | 6 | {% block title %}{{ resumator_basicinformation.name }} - Publications{% endblock %} 7 | 8 | {% block extracontent %} 9 |
    Publications
    10 | {% for publication in resumator_publication %} 11 | 32 | {% endfor %} 33 | {% endblock %} -------------------------------------------------------------------------------- /resumator/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ammsa/django-resumator/0939efe30abbd679fd3fdcd9903f3b7be80b87d8/resumator/templatetags/__init__.py -------------------------------------------------------------------------------- /resumator/templatetags/base_extras.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | import resumator 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.simple_tag 8 | def get_version(): 9 | return resumator.__version__ 10 | -------------------------------------------------------------------------------- /resumator/templatetags/experience_extras.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | @register.filter 6 | def get_item(dictionary, key): 7 | return dictionary.get(key) 8 | -------------------------------------------------------------------------------- /resumator/templatetags/projects_extras.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | register = template.Library() 4 | 5 | @register.filter 6 | def get_item(dictionary, key): 7 | return dictionary.get(key) 8 | -------------------------------------------------------------------------------- /resumator/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /resumator/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | url(r'^$', views.about, name='index'), 7 | url(r'^about/$', views.about, name='index'), 8 | url(r'^projects/$', views.projects, name='index'), 9 | url(r'^experience/$', views.experience, name='index'), 10 | url(r'^publications/$', views.publications, name='index'), 11 | url(r'^education/$', views.education, name='index'), 12 | ] 13 | -------------------------------------------------------------------------------- /resumator/utils.py: -------------------------------------------------------------------------------- 1 | from django.apps import apps 2 | 3 | 4 | def used_models(context_dict, ignore_models=None): 5 | """ 6 | Checks if models other than whats in `context_dict` or `ignore_models` 7 | have atleast a single instance in db 8 | :return context_dict: dict containing already assigned objects. 9 | sets models names as key and value set to True 10 | if they have atleast a single instance in db. 11 | Ignores models in `ignore_models` 12 | """ 13 | app_models = apps.get_app_config('resumator').get_models() 14 | for model in app_models: 15 | if model._meta.db_table in context_dict or \ 16 | ignore_models and model._meta.db_table in ignore_models: 17 | pass 18 | else: 19 | if model.objects.all(): 20 | context_dict[model._meta.db_table] = True 21 | return context_dict 22 | -------------------------------------------------------------------------------- /resumator/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.template import RequestContext, loader 3 | 4 | from .models import BasicInformation 5 | from .models import Project 6 | from .models import Experience 7 | from .models import Publication 8 | from .models import Education 9 | from .models import Settings 10 | from resumator import utils 11 | 12 | 13 | def about(request): 14 | settings = Settings.objects.get() 15 | basic_information = BasicInformation.objects.get() 16 | context_dict = {"resumator_basicinformation": basic_information, 17 | "resumator_settings": settings} 18 | context = RequestContext(request, 19 | utils.used_models(context_dict)) 20 | template = loader.get_template('resumator/index.html') 21 | return HttpResponse(template.render(context)) 22 | 23 | 24 | def projects(request): 25 | settings = Settings.objects.get() 26 | basic_information = BasicInformation.objects.latest('pk') 27 | projects_list = Project.objects.all() 28 | used_languages = {} 29 | for project in projects_list: 30 | used_languages.update(project.get_languages()) 31 | context_dict = {'resumator_basicinformation': basic_information, 32 | 'resumator_project': projects_list, 33 | 'used_languages': used_languages, 34 | "resumator_settings": settings 35 | } 36 | context = RequestContext(request, 37 | utils.used_models(context_dict, 38 | ignore_models=["resumator_language"])) 39 | template = loader.get_template('resumator/projects.html') 40 | return HttpResponse(template.render(context)) 41 | 42 | 43 | def experience(request): 44 | settings = Settings.objects.get() 45 | basic_information = BasicInformation.objects.latest('pk') 46 | experience_list = Experience.objects.all() 47 | used_languages = {} 48 | for experience in experience_list: 49 | used_languages.update(experience.get_languages()) 50 | context_dict = {'resumator_basicinformation': basic_information, 51 | 'resumator_experience': experience_list, 52 | 'used_languages': used_languages, 53 | "resumator_settings": settings 54 | } 55 | context = RequestContext(request, 56 | utils.used_models(context_dict)) 57 | template = loader.get_template('resumator/experience.html') 58 | return HttpResponse(template.render(context)) 59 | 60 | 61 | def publications(request): 62 | settings = Settings.objects.get() 63 | basic_information = BasicInformation.objects.latest('pk') 64 | publication_list = Publication.objects.all() 65 | context_dict = {'resumator_basicinformation': basic_information, 66 | 'resumator_publication': publication_list, 67 | "resumator_settings": settings 68 | } 69 | context = RequestContext(request, 70 | utils.used_models(context_dict)) 71 | template = loader.get_template('resumator/publications.html') 72 | return HttpResponse(template.render(context)) 73 | 74 | 75 | def education(request): 76 | settings = Settings.objects.get() 77 | basic_information = BasicInformation.objects.latest('pk') 78 | education_list = Education.objects.all() 79 | context_dict = {'resumator_basicinformation': basic_information, 80 | 'resumator_education': education_list, 81 | "resumator_settings": settings 82 | } 83 | context = RequestContext(request, 84 | utils.used_models(context_dict)) 85 | template = loader.get_template('resumator/education.html') 86 | return HttpResponse(template.render(context)) 87 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | import django 6 | from django.conf import settings 7 | from django.test.utils import get_runner 8 | 9 | if __name__ == "__main__": 10 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings' 11 | django.setup() 12 | TestRunner = get_runner(settings) 13 | test_runner = TestRunner() 14 | test_runner.setup_test_environment() 15 | old_config = test_runner.setup_databases() 16 | failures = test_runner.run_tests(["tests"]) 17 | sys.exit(bool(failures)) 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: 5 | README = readme.read() 6 | 7 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 8 | 9 | setup( 10 | name='django-resumator', 11 | version='1.1.6', 12 | packages=['resumator'], 13 | install_requires=[ 14 | 'django-solo>=1.1.0', 15 | 'Pillow>=3.0.0', 16 | ], 17 | include_package_data=True, 18 | license='MIT License', 19 | description='A lightweight Django app to create Web-based resumes.', 20 | long_description=README, 21 | url='https://github.com/AmmsA/django-resumator', 22 | author='Mustafa S', 23 | author_email='mabualsaud@outlook.com', 24 | classifiers=[ 25 | 'Environment :: Web Environment', 26 | 'Framework :: Django', 27 | 'Intended Audience :: Developers', 28 | 'License :: OSI Approved :: MIT License', 29 | 'Operating System :: OS Independent', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: 3', 32 | 'Programming Language :: Python :: 3.2', 33 | 'Programming Language :: Python :: 3.3', 34 | 'Topic :: Internet :: WWW/HTTP', 35 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 36 | ], 37 | ) 38 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ammsa/django-resumator/0939efe30abbd679fd3fdcd9903f3b7be80b87d8/tests/__init__.py -------------------------------------------------------------------------------- /tests/models.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import ugettext_lazy as _ 2 | from django.db import models 3 | from django.core.exceptions import ValidationError 4 | from django.core.exceptions import ObjectDoesNotExist 5 | 6 | from solo.models import SingletonModel 7 | 8 | 9 | class TestBasicInformation(SingletonModel): 10 | name = models.CharField(max_length=25, 11 | default="John Smith") 12 | short_bio = models.CharField(max_length=100, 13 | blank=True, 14 | verbose_name=_("short bio"), 15 | default="My short bio") 16 | long_bio = models.TextField(blank=True, 17 | verbose_name=_("long bio"), 18 | default="My long bio") 19 | email = models.EmailField(default="email@example.com") 20 | github = models.URLField(blank=True) 21 | linkedin = models.URLField(blank=True) 22 | image = models.ImageField(upload_to="media/images", blank=True) 23 | 24 | 25 | class TestEducation(models.Model): 26 | name = models.CharField(max_length=50, 27 | verbose_name=_("University name")) 28 | abbreviation = models.CharField(max_length=10, 29 | blank=True, 30 | default=None, 31 | verbose_name=_("Degree abbreviation")) 32 | start_date = models.DateField(null=True, blank=True, 33 | verbose_name=_("start date")) 34 | end_date = models.DateField(null=True, 35 | blank=True, 36 | verbose_name=_("end date")) 37 | major = models.CharField(max_length=50, 38 | blank=True, 39 | default=None) 40 | gpa = models.CharField(max_length=10, 41 | blank=True, 42 | default=None, 43 | verbose_name=_("GPA")) 44 | 45 | def save(self, *args, **kwargs): 46 | self.clean() 47 | return super(self.__class__, self).save(*args, **kwargs) 48 | 49 | def clean(self): 50 | if self.start_date > self.end_date: 51 | raise ValidationError({"start_date": _("Start date must be before end date."), 52 | "end_date": _("Start date must be before end date.")}) 53 | 54 | class Meta: 55 | ordering = ['-end_date'] 56 | 57 | 58 | class TestPublication(models.Model): 59 | title = models.CharField(max_length=100) 60 | authors = models.CharField(max_length=200, 61 | blank=True) 62 | conference = models.CharField(max_length=200, 63 | blank=True) 64 | abstract = models.TextField(blank=True) 65 | year = models.CharField(max_length=4, 66 | blank=True) 67 | link = models.URLField(blank=True) 68 | 69 | class Meta: 70 | ordering = ['-year'] 71 | 72 | 73 | class TestProject(models.Model): 74 | name = models.CharField(max_length=50) 75 | description = models.TextField(default=None, 76 | blank=True, 77 | verbose_name=_("description")) 78 | start_date = models.DateField(null=True, 79 | blank=True, 80 | verbose_name=_("start date")) 81 | end_date = models.DateField(null=True, 82 | blank=True, 83 | verbose_name=_("end date")) 84 | link = models.URLField(blank=True) 85 | 86 | def save(self, *args, **kwargs): 87 | self.clean() 88 | return super(self.__class__, self).save(*args, **kwargs) 89 | 90 | def clean(self): 91 | if self.start_date > self.end_date: 92 | raise ValidationError({"start_date": _("Start date must be before end date."), 93 | "end_date": _("Start date must be before end date.")}) 94 | 95 | def get_languages(self): 96 | languages = TestLanguage.objects.all() 97 | used_languages = [] 98 | for language in languages: 99 | try: 100 | language.projects.get(pk=self.pk) 101 | used_languages.append(language) 102 | except ObjectDoesNotExist: 103 | pass 104 | return {self: used_languages} 105 | 106 | 107 | class TestExperience(models.Model): 108 | company = models.CharField(max_length=50) 109 | role = models.CharField(max_length=150) 110 | start_date = models.DateField(null=True, 111 | blank=True, 112 | verbose_name=_("start date")) 113 | end_date = models.DateField(null=True, 114 | blank=True, 115 | verbose_name=_("end date")) 116 | description = models.TextField(default=None, 117 | verbose_name=_("description")) 118 | link = models.URLField(blank=True) 119 | image = models.ImageField(blank=True) 120 | 121 | class Meta: 122 | ordering = ['-end_date'] 123 | 124 | def save(self, *args, **kwargs): 125 | self.clean() 126 | return super(self.__class__, self).save(*args, **kwargs) 127 | 128 | def clean(self): 129 | if self.start_date > self.end_date: 130 | raise ValidationError({"start_date": _("Start date must be before end date."), 131 | "end_date": _("Start date must be before end date.")}) 132 | 133 | def get_languages(self): 134 | languages = TestLanguage.objects.all() 135 | used_languages = [] 136 | for language in languages: 137 | try: 138 | language.experience.get(pk=self.pk) 139 | used_languages.append(language) 140 | except ObjectDoesNotExist: 141 | pass 142 | return {self: used_languages} 143 | 144 | 145 | class TestLanguage(models.Model): 146 | name = models.CharField(max_length=50) 147 | experience = models.ManyToManyField(TestExperience, 148 | blank=True) 149 | projects = models.ManyToManyField(TestProject, 150 | blank=True) 151 | -------------------------------------------------------------------------------- /tests/test_models.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.db import IntegrityError 3 | from django.core.exceptions import ValidationError 4 | from datetime import datetime 5 | from datetime import timedelta 6 | 7 | from .models import TestBasicInformation as BasicInformation 8 | from .models import TestExperience as Experience 9 | from .models import TestEducation as Education 10 | from .models import TestProject as Project 11 | from .models import TestPublication as Publication 12 | from .models import TestLanguage as Language 13 | 14 | 15 | class BasicInformationTestCase(TestCase): 16 | 17 | def test_multiple_basic_information__raises_IntegrityError(self): 18 | with self.assertRaises(IntegrityError): 19 | BasicInformation.objects.create(name="James") 20 | BasicInformation.objects.create(name="John") 21 | 22 | 23 | class EducationTestCase(TestCase): 24 | 25 | def test_start_date_after_end_date__raises_ValidationError(self): 26 | with self.assertRaises(ValidationError): 27 | today = datetime.today() 28 | yesterday = datetime.now() - timedelta(hours=24) 29 | Education.objects.create(name="blah", 30 | abbreviation="blah", 31 | start_date=today, 32 | end_date=yesterday, 33 | major="blah", 34 | gpa="4.0") 35 | 36 | 37 | class ExperienceTestCase(TestCase): 38 | def setUp(self): 39 | self.experience = Experience.objects.create(company="blah", 40 | description="blah") 41 | 42 | def test_start_date_after_end_date__raises_ValidationError(self): 43 | with self.assertRaises(ValidationError): 44 | today = datetime.today() 45 | yesterday = datetime.now() - timedelta(hours=24) 46 | Experience.objects.create(company="blah", 47 | role="blah", 48 | start_date=today, 49 | end_date=yesterday) 50 | 51 | def test_project_with_languages_assigned__returns_languages(self): 52 | python = Language.objects.create(name="Python") 53 | django = Language.objects.create(name="Django") 54 | python.experience.add(self.experience) 55 | django.experience.add(self.experience) 56 | expected = {self.experience: [python, django]} 57 | self.assertDictEqual(expected, self.experience.get_languages()) 58 | 59 | 60 | class ProjectTestCase(TestCase): 61 | def setUp(self): 62 | self.project = Project.objects.create(name="blah", 63 | description="blah") 64 | 65 | def test_start_date_after_end_date__raises_ValidationError(self): 66 | with self.assertRaises(ValidationError): 67 | today = datetime.today() 68 | yesterday = datetime.now() - timedelta(hours=24) 69 | Project.objects.create(name="blah", 70 | description="blah", 71 | start_date=today, 72 | end_date=yesterday) 73 | 74 | def test_project_with_languages_assigned__returns_languages(self): 75 | python = Language.objects.create(name="Python") 76 | django = Language.objects.create(name="Django") 77 | python.projects.add(self.project) 78 | django.projects.add(self.project) 79 | expected = {self.project: [python, django]} 80 | self.assertDictEqual(expected, self.project.get_languages()) 81 | 82 | 83 | class PublicationTestCase(TestCase): 84 | pass 85 | 86 | 87 | class LanguageTestCase(TestCase): 88 | pass 89 | -------------------------------------------------------------------------------- /tests/test_settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 4 | DEBUG = True 5 | 6 | ROOT_URLCONF = 'theresumator.urls' 7 | 8 | SECRET_KEY = 'fake-key' 9 | INSTALLED_APPS = ( 10 | 'django.contrib.auth', 11 | 'django.contrib.contenttypes', 12 | 'django.contrib.sessions', 13 | 'django.contrib.admin', 14 | "tests", 15 | "resumator", 16 | ) 17 | 18 | DATABASES = { 19 | 'default': { 20 | 'ENGINE': 'django.db.backends.sqlite3', 21 | 'NAME': ':memory:', 22 | 'TEST_NAME': ':memory:', 23 | } 24 | } 25 | 26 | TEMPLATES = [ 27 | { 28 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 29 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 30 | 'APP_DIRS': True, 31 | 'OPTIONS': { 32 | 'context_processors': [ 33 | 'django.template.context_processors.debug', 34 | 'django.template.context_processors.request', 35 | 'django.contrib.auth.context_processors.auth', 36 | 'django.contrib.messages.context_processors.messages', 37 | ], 38 | }, 39 | }, 40 | ] 41 | 42 | 43 | MEDIA_ROOT = os.path.join(BASE_DIR, 'site_media') 44 | MEDIA_URL = '/' 45 | 46 | STATIC_URL = '/static/' 47 | STATICFILES_DIRS = ( 48 | os.path.join(BASE_DIR, "static"), 49 | '/var/www/static/', 50 | ) 51 | --------------------------------------------------------------------------------