├── .codacy.yml ├── .coveragerc ├── .editorconfig ├── .env.example ├── .github ├── config.yml └── workflows │ ├── commitlint.yml │ ├── documentation.yml │ └── main.yml ├── .gitignore ├── AUTOMATED_TESTING.md ├── CONTRIBUTING.md ├── Dockerfile ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── code_of_conduct.md ├── commitlint.config.js ├── docker-compose.yml ├── docs ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── docs │ ├── Commit-Message-Style-Guide.md │ ├── Home.md │ ├── How-To-Contribute.md │ ├── Installation-Instructions-for-OSX.md │ ├── Test-The-App.md │ ├── adding-pages.md │ ├── code-organization.md │ ├── coding-standards.md │ ├── community-creation.md │ ├── community-resources.md │ ├── database-design.md │ ├── environment-variables.md │ ├── future-ideas.md │ ├── groups-permissions.md │ ├── introduction.md │ ├── maintainer-guideline.md │ ├── meetups-creation.md │ ├── tech-stack.md │ ├── technical-decisions.md │ ├── user-auth.md │ └── zoom-meetings.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ └── css │ │ └── custom.css ├── static │ ├── .nojekyll │ └── img │ │ ├── favicon.ico │ │ └── logo.svg └── yarn.lock ├── pytest.ini ├── reporting_guidelines.md ├── requirements ├── dev.txt └── prod.txt ├── setup.cfg └── systers_portal ├── GeoLite2-City_20200616 ├── COPYRIGHT.txt ├── GeoLite2-City.mmdb ├── LICENSE.txt └── README.txt ├── __init__.py ├── blog ├── __init__.py ├── admin.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20200724_2045.py │ ├── 0003_userpins.py │ └── __init__.py ├── mixins.py ├── models.py ├── tests │ ├── __init__.py │ ├── test_forms.py │ ├── test_mixins.py │ ├── test_models.py │ └── test_views.py ├── urls.py └── views.py ├── common ├── __init__.py ├── admin.py ├── forms.py ├── helpers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20200724_2045.py │ └── __init__.py ├── mixins.py ├── models.py ├── templatetags │ ├── __init__.py │ └── verbose_name.py ├── tests │ ├── __init__.py │ ├── selenium │ │ ├── base.py │ │ ├── credentials.json │ │ ├── test_admin_actions.py │ │ ├── test_auth_pages.py │ │ ├── test_can_goto_common_pages.py │ │ └── test_user_actions.py │ ├── test_forms.py │ ├── test_helpers.py │ ├── test_mixins.py │ ├── test_models.py │ ├── test_templatetags.py │ └── test_views.py └── views.py ├── community ├── __init__.py ├── admin.py ├── apps.py ├── constants.py ├── context_processors.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20200724_2045.py │ └── __init__.py ├── mixins.py ├── models.py ├── permissions.py ├── signals.py ├── tests │ ├── __init__.py │ ├── selenium │ │ ├── conftest.py │ │ ├── test_community_admin_actions.py │ │ ├── test_community_page.py │ │ └── test_user_actions.py │ ├── test_constants.py │ ├── test_forms.py │ ├── test_mixins.py │ ├── test_models.py │ ├── test_permissions.py │ ├── test_signals.py │ ├── test_utils.py │ └── test_views.py ├── urls.py ├── utils.py └── views.py ├── manage.py ├── meetup ├── __init__.py ├── admin.py ├── apps.py ├── compare.py ├── constants.py ├── fixtures │ └── meetups_seed.json ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20200804_0509.py │ ├── 0003_auto_20200804_0527.py │ ├── 0004_auto_20200804_0538.py │ ├── 0005_meetup_start_url.py │ ├── 0006_auto_20200805_0639.py │ ├── 0007_auto_20200805_0641.py │ ├── 0008_meetup_meeting_id.py │ └── __init__.py ├── models.py ├── permissions.py ├── signals.py ├── tests │ ├── selenium │ │ ├── conftest.py │ │ ├── test_meetup_location_admin_page.py │ │ ├── test_meetup_location_page.py │ │ ├── test_meetup_locations_page.py │ │ └── test_meetup_page.py │ ├── test_constants.py │ ├── test_forms.py │ ├── test_models.py │ ├── test_permissions.py │ ├── test_signals.py │ ├── test_utils.py │ └── test_views.py ├── urls.py ├── utils.py └── views.py ├── membership ├── __init__.py ├── admin.py ├── constants.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20200724_2045.py │ └── __init__.py ├── models.py ├── tests │ ├── __init__.py │ ├── test_forms.py │ ├── test_models.py │ └── test_views.py ├── urls.py └── views.py ├── static ├── css │ ├── bootstrap.min.css │ ├── date-time-picker │ │ ├── classic.css │ │ ├── classic.date.css │ │ └── classic.time.css │ ├── font-awesome.min.css │ └── style.css ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ ├── fontawesome-webfont.woff2 │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── img │ ├── about_systers.jpg │ ├── default.png │ ├── facebook.png │ ├── favicon.ico │ ├── github.svg │ ├── google.jpg │ ├── logo.png │ ├── systers_communities.jpg │ ├── systers_craw.jpg │ ├── systers_initiatives.jpg │ ├── systers_technical_interests.jpg │ └── twitter.png └── js │ ├── choose_profile_pic.js │ ├── community-search.js │ ├── libs │ ├── bootstrap.min.js │ ├── date-time-picker │ │ ├── picker.date.js │ │ ├── picker.js │ │ └── picker.time.js │ └── jquery-1.11.1.min.js │ ├── pin.js │ ├── search_menu.js │ └── select.js ├── systers_portal ├── __init__.py ├── settings │ ├── __init__.py │ ├── base.py │ ├── dev.py │ ├── docker.py │ ├── production.py │ └── testing.py ├── urls.py └── wsgi.py ├── templates ├── 403.html ├── 404.html ├── 500.html ├── account │ ├── account_inactive.html │ ├── email.html │ ├── email_confirm.html │ ├── login.html │ ├── logout.html │ ├── password_change.html │ ├── password_reset.html │ ├── signup.html │ └── verification_sent.html ├── base.html ├── blog │ ├── post.html │ ├── post_list.html │ ├── snippets │ │ ├── news_sidebar.html │ │ ├── pagination.html │ │ ├── resource_types.html │ │ ├── resources_sidebar.html │ │ └── tags_sidebar.html │ └── tag_type.html ├── bootstrap3 │ └── layout │ │ └── checkboxselectmultiple.html ├── common │ ├── about_us.html │ ├── add_post.html │ ├── contact.html │ ├── edit_post.html │ ├── index.html │ ├── new_community_proposal.html │ └── post_confirm_delete.html ├── community │ ├── add_community.html │ ├── base.html │ ├── community_search.html │ ├── confirm_reject_request_community.html │ ├── edit_community_request.html │ ├── edit_profile.html │ ├── new_community_requests.html │ ├── page.html │ ├── permissions.html │ ├── request_community.html │ ├── snippets │ │ ├── community-map.html │ │ ├── community-marker.html │ │ ├── community_sidebar.html │ │ ├── footer.html │ │ ├── join_button.html │ │ ├── page_sidebar.html │ │ └── search-snippet.html │ ├── users.html │ ├── view_community_request.html │ ├── view_profile.html │ └── weekly_digest_email.html ├── meetup │ ├── about.html │ ├── add_comment.html │ ├── add_meetup.html │ ├── add_meetup_location.html │ ├── add_member.html │ ├── add_support_request.html │ ├── base.html │ ├── comment_confirm_delete.html │ ├── confirm_reject_request_meetup.html │ ├── confirm_reject_request_meetup_location.html │ ├── edit_comment.html │ ├── edit_meetup.html │ ├── edit_meetup_location.html │ ├── edit_meetup_location_request.html │ ├── edit_support_request.html │ ├── join_requests.html │ ├── list_location.html │ ├── list_meetup.html │ ├── list_support_requests.html │ ├── location_change_email.html │ ├── meetup.html │ ├── meetup_confirm_delete.html │ ├── meetup_location_confirm_delete.html │ ├── members.html │ ├── new_meetup_location_requests.html │ ├── new_meetup_requests.html │ ├── past_meetups.html │ ├── reminder.html │ ├── request_new_meetup.html │ ├── request_new_meetup_location.html │ ├── request_virtual_meetup.html │ ├── rsvp_going.html │ ├── rsvp_meetup.html │ ├── snippets │ │ ├── about_button.html │ │ ├── add_resource_button.html │ │ ├── all_upcoming_meetups_list.html │ │ ├── join_button.html │ │ ├── meetup_carousel.html │ │ ├── meetup_location_sidebar.html │ │ ├── meetup_locations_grid.html │ │ ├── meetup_locations_map.html │ │ ├── meetup_sidebar.html │ │ ├── share_buttons.html │ │ ├── systers_twitter_feed.html │ │ ├── upcoming_meetups_list.html │ │ ├── user-cell.html │ │ └── users-grid.html │ ├── sponsors.html │ ├── support_request.html │ ├── support_request_confirm_delete.html │ ├── time_change_email.html │ ├── unapproved_support_requests.html │ ├── upcoming_meetups.html │ ├── view_new_meetup_location_request.html │ └── view_new_meetup_request.html ├── membership │ ├── join_requests.html │ └── transfer_ownership.html ├── pinax │ └── notifications │ │ ├── joined_meetup_location │ │ ├── full.txt │ │ └── short.txt │ │ ├── made_moderator │ │ ├── full.txt │ │ └── short.txt │ │ ├── new_join_request │ │ ├── full.txt │ │ └── short.txt │ │ ├── new_meetup │ │ ├── full.txt │ │ └── short.txt │ │ ├── new_meetup_request │ │ ├── full.txt │ │ └── short.txt │ │ ├── new_support_request │ │ ├── full.txt │ │ └── short.txt │ │ └── support_request_approved │ │ ├── full.txt │ │ └── short.txt ├── socialaccount │ └── snippets │ │ └── provider_list.html └── users │ ├── edit_profile.html │ ├── pins.html │ ├── settings.html │ ├── snippets │ ├── membership.html │ ├── permissions.html │ └── profile.html │ └── view_profile.html └── users ├── __init__.py ├── adapter.py ├── admin.py ├── apps.py ├── forms.py ├── migrations ├── 0001_initial.py ├── 0002_auto_20200825_1932.py └── __init__.py ├── models.py ├── scheduler.py ├── signals.py ├── tests ├── __init__.py ├── test_forms.py ├── test_models.py ├── test_signals.py └── test_views.py ├── urls.py └── views.py /.codacy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | exclude_paths: 3 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | */migrations/* 4 | */tests/* 5 | systers_portal/manage.py 6 | systers_portal/systers_portal/* 7 | */admin.py 8 | */site-packages/* 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.{html,json}] 12 | indent_style = space 13 | indent_size = 2 14 | trim_trailing_whitespace = false 15 | 16 | [*.{css,js}] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | 21 | # Customizations for third party libraries 22 | 23 | [static/js/lib/**] 24 | trim_trailing_whitespace = ignore 25 | insert_final_newline = ignore 26 | indent_style = ignore 27 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SECRET_KEY = foobarbaz 2 | DEBUG = True 3 | ALLOWED_HOSTS = * 4 | 5 | # Database settings 6 | 7 | DB_NAME = systersdb 8 | DB_USER = 9 | DB_PASSWORD = 10 | DB_HOST = localhost 11 | DB_PORT = 5432 12 | 13 | # Email settings 14 | 15 | EMAIL_HOST = localhost 16 | EMAIL_PORT = 1025 17 | 18 | # External APIs 19 | 20 | GOOGLE_MAPS_API_KEY = 21 | ZOOM_API_KEY = 22 | ZOOM_API_SECRET = 23 | ZOOM_USER_ID = 24 | -------------------------------------------------------------------------------- /.github/workflows/commitlint.yml: -------------------------------------------------------------------------------- 1 | # This action uses commitlint to lint commit messages and check for the required guidelines 2 | # as given by the config file commitlint.config.js 3 | 4 | name: Check Commit Messages 5 | 6 | on: 7 | pull_request: 8 | branches: [ develop ] 9 | 10 | jobs: 11 | check-commits: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2.3.1 15 | with: 16 | fetch-depth: 0 17 | - uses: actions/setup-node@v2.1.0 18 | - run: yarn add @commitlint/{config-conventional,cli} 19 | - run: yarn run commitlint --from HEAD~${{ github.event.pull_request.commits }} --to HEAD 20 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Build and Deploy Documentation 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the develop branch 8 | push: 9 | branches: [ develop ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | # This workflow contains a single job called "build" 17 | build: 18 | # The type of runner that the job will run on 19 | runs-on: ubuntu-latest 20 | 21 | # Steps represent a sequence of tasks that will be executed as part of the job 22 | steps: 23 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 24 | 25 | - name: Checkout 🛎️ 26 | uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly. 27 | with: 28 | persist-credentials: false 29 | 30 | - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. 31 | run: | 32 | cd docs 33 | npm install 34 | npm run build 35 | 36 | - name: Deploy 🚀 37 | uses: JamesIves/github-pages-deploy-action@3.7.1 38 | with: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | BRANCH: documentation # The branch the action should deploy to. 41 | FOLDER: docs/build # The folder the action should deploy. 42 | CLEAN: true # Automatically remove deleted files from the deploy branch 43 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | pull_request: 7 | branches: [ develop ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-18.04 12 | strategy: 13 | matrix: 14 | python-version: [3.6, 3.7, 3.8] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v1 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | 23 | - name: Install Dependencies 24 | run: | 25 | cp .env.example .env 26 | python -m pip install --upgrade pip 27 | pip install -r requirements/dev.txt 28 | sudo apt-get install python-gdal 29 | python -c "import nltk; nltk.download('punkt'); nltk.download('stopwords')" 30 | 31 | - name: Run Flake Test 32 | run: flake8 systers_portal 33 | 34 | - name: Run Tests 35 | run: coverage run systers_portal/manage.py test --settings=systers_portal.settings.testing 36 | 37 | - name: Upload coverage to Codecov 38 | uses: codecov/codecov-action@v1 39 | with: 40 | name: codecov-umbrella 41 | fail_ci_if_error: true 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | ### Django ### 4 | *.log 5 | *.pot 6 | *.pyc 7 | local_settings.py 8 | 9 | ### Python ### 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | 13 | 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Packages 19 | *.egg 20 | *.egg-info 21 | dist 22 | build 23 | eggs 24 | parts 25 | bin 26 | var 27 | sdist 28 | develop-eggs 29 | .installed.cfg 30 | lib 31 | lib64 32 | __pycache__ 33 | .Python 34 | env/ 35 | build/ 36 | develop-eggs/ 37 | dist/ 38 | downloads/ 39 | eggs/ 40 | .eggs/ 41 | parts/ 42 | sdist/ 43 | var/ 44 | *.egg-info/ 45 | 46 | # PyInstaller 47 | # Usually these files are written by a python script from a template 48 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 49 | *.manifest 50 | *.spec 51 | 52 | # Installer logs 53 | pip-log.txt 54 | pip-delete-this-directory.txt 55 | 56 | # Unit test / coverage reports 57 | htmlcov/ 58 | .tox/ 59 | .coverage 60 | .cache 61 | nosetests.xml 62 | coverage.xml 63 | 64 | # Vagrant 65 | .vagrant* 66 | 67 | # Vim 68 | *~ 69 | *.swo 70 | *.swk 71 | *.swn 72 | *.swo 73 | *.swo 74 | 75 | # Translations 76 | *.mo 77 | *.pot 78 | 79 | # Mr Developer 80 | .mr.developer.cfg 81 | .project 82 | .pydevproject 83 | 84 | # PyBuilder 85 | target/ 86 | 87 | # Virtualenv 88 | venv 89 | 90 | .lesshst 91 | .viminfo 92 | .zcompdump 93 | .zshrc 94 | LOGS/ 95 | bin/ 96 | include/ 97 | local/ 98 | *.sql 99 | 100 | # PyCharm IDE 101 | .idea 102 | 103 | #VS Code IDE 104 | .vscode/ 105 | 106 | # Documentation build 107 | _build 108 | 109 | # pytest 110 | 111 | .pytest_cache 112 | 113 | # Environment variables 114 | .env 115 | 116 | # gensim 117 | workdir.0 118 | -------------------------------------------------------------------------------- /AUTOMATED_TESTING.md: -------------------------------------------------------------------------------- 1 | Automated Testing 2 | ================= 3 | 4 | Documentation on running automated tests with selenium for systers/portal. 5 | 6 | Setup Selenium (Unix) 7 | --------------------- 8 | 9 | To run selenium test you would need to install (download) the standalone servers which implements WebDrivers' wire protocols : 10 | * geckodriver and 11 | * chromedriver 12 | 13 | is used here not withstanding, other WebDriver servers may be used with a little tweaking (which entails adding the desired webdriver in the browsers dictionary in the conftest.py) 14 | 15 | ### Installation 16 | 17 | 1. Download chromedriver [here](https://sites.google.com/a/chromium.org/chromedriver/home) 18 | 1. Download geckodriver [here](https://github.com/mozilla/geckodriver/releases) 19 | 20 | Get latest versions, and make sure the version downloaded is meant for your OS and is compatible (32/64 bits) 21 | 22 | After downloading, extract both drivers and save in a directory (e.g webdrivers) in a location of choice, finally let selenium know where it can find the webdrivers by exporting the paths to the various executables, you would have to do this every time your (re)start you venv (or any enviroment for that matter) 23 | 24 | ```bash 25 | export PATH=$PATH:/path/to/directory/of/executable/downloaded/ 26 | ``` 27 | 28 | That is quite boring you can just make each of them available globally once and for all 29 | 30 | ```bash 31 | sudo mv geckodriver /usr/local/bin 32 | sudo mv chromedriver /usr/local/bin 33 | ``` 34 | 35 | You can also write a .bashrc script and export, to avoid the repetition this method however has thesame effect as simply moving the files to ```/usr/local/bin``` above. 36 | 37 | Note : Be sure to export only the directory(ies) containing the executables without including the executables to avoid the "NotADirectoryError: [Errno 20] Not a directory" 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | * You can join our [AnitaB.org Open Source Zulip channel](https://anitab-org.zulipchat.com/). Each active repo has its own channel to direct questions to (for example #powerup or [#portal](https://anitab-org.zulipchat.com/#narrow/stream/222540-portal)). 3 | * Remember that this is an inclusive community, committed to creating a safe, positive environment. See the full [Code of Conduct](http://www.systers.io/code-of-conduct.html). 4 | ## General Guidelines 5 | * If you’re just getting started work on an issue labeled “First Timers Only” in any project. Additional resources are available on our [website](http://www.systers.io). 6 | * In an active repository (not an archived one), choose an open issue from the issue list, claim it in the comments, and a maintainer will assign it to you. 7 | * After approval you must make continuous notes on your progress in the issue while working. If there is not at least one comment every 3 days, the maintainer can reassign the issue. 8 | * When sending a PR have an appropriate title referencing the issue which it solves. Add “fixes #” in the commit body, so that when the PR gets merged, the issue gets closed automatically. Do not do this if the PR solves only a part of the issue. See more information on commit guidelines [here](https://udacity.github.io/git-styleguide/). 9 | * If you’d like to create a new issue, please go through our issue list first (open as well as closed) and make sure the issues you are reporting do not replicate the existing issues. 10 | * Have a short description on what has gone wrong (like a root cause analysis and description of the fix), if that information is not already present in the issue. 11 | * If you have issues on multiple pages, report them separately. Do not combine them into a single issue. 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2.7 2 | MAINTAINER Ana Balica 3 | EXPOSE 8000 4 | 5 | WORKDIR /usr/src 6 | RUN mkdir portal 7 | RUN cd portal 8 | WORKDIR /usr/src/portal 9 | COPY requirements/prod.txt /usr/src/portal/requirements/prod.txt 10 | RUN pip install --no-cache-dir -r requirements/prod.txt 11 | 12 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | As a [USER], 3 | I need [TO DO THIS], 4 | so that I can [ACCOMPLISH THAT]. 5 | 6 | ## Mocks 7 | [INSERT RELEVANT PNG FILE] 8 | 9 | ## Acceptance Criteria 10 | ### Update [Required] 11 | - [ ] [LIST ITEMS] 12 | ### Enhancement to Update [Optional] 13 | - [ ] [LIST ITEMS] 14 | 15 | ## Definition of Done 16 | - [ ] All of the required items are completed. 17 | - [ ] Approval by 1 mentor. 18 | 19 | ## Estimation 20 | [INSERT NUMBER HERE] hours 21 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | Include a summary of the change and relevant motivation/context. List any dependencies that are required for this change. 3 | 4 | Fixes # [ISSUE] 5 | 6 | ### Type of Change: 7 | **Delete irrelevant options.** 8 | 9 | - Code 10 | - Quality Assurance 11 | - User Interface 12 | - Outreach 13 | - Documentation 14 | 15 | **Code/Quality Assurance Only** 16 | - Bug fix (non-breaking change which fixes an issue) 17 | - This change requires a documentation update (software upgrade on readme file) 18 | - New feature (non-breaking change which adds functionality pre-approved by mentors) 19 | 20 | 21 | 22 | ### How Has This Been Tested? 23 | Describe the tests you ran to verify your changes. Provide instructions or GIFs so we can reproduce. List any relevant details for your test. 24 | 25 | 26 | ### Checklist: 27 | **Delete irrelevant options.** 28 | 29 | - [ ] My PR follows the style guidelines of this project 30 | - [ ] I have performed a self-review of my own code or materials 31 | - [ ] I have commented my code or provided relevant documentation, particularly in hard-to-understand areas 32 | - [ ] I have made corresponding changes to the documentation 33 | - [ ] Any dependent changes have been merged 34 | 35 | **Code/Quality Assurance Only** 36 | - [ ] My changes generate no new warnings 37 | - [ ] My PR currently breaks something (fix or feature that would cause existing functionality to not work as expected) 38 | - [ ] I have added tests that prove my fix is effective or that my feature works 39 | - [ ] New and existing unit tests pass locally with my changes 40 | - [ ] Any dependent changes have been published in downstream modules 41 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserPreset: 'conventional-changelog-conventionalcommits', 3 | rules: { 4 | 'body-leading-blank': [1, 'always'], 5 | 'body-max-line-length': [2, 'always', 100], 6 | 'footer-leading-blank': [1, 'always'], 7 | 'footer-max-line-length': [2, 'always', 100], 8 | 'header-max-length': [2, 'always', 100], 9 | 'scope-case': [2, 'always', 'lower-case'], 10 | 'subject-case': [ 11 | 2, 12 | 'always', 13 | ['sentence-case', 'upper-case', 'lower-case'] 14 | ], 15 | 'subject-empty': [2, 'never'], 16 | 'subject-full-stop': [2, 'never', '.'], 17 | 'type-case': [2, 'always', 'lower-case'], 18 | 'type-empty': [2, 'never'], 19 | 'type-enum': [ 20 | 2, 21 | 'always', 22 | [ 23 | 'build', 24 | 'chore', 25 | 'ci', 26 | 'docs', 27 | 'feat', 28 | 'fix', 29 | 'perf', 30 | 'refactor', 31 | 'revert', 32 | 'style', 33 | 'test' 34 | ] 35 | ] 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | db: 2 | image: postgres:9.4 3 | web: 4 | build: . 5 | command: python systers_portal/manage.py runserver 0.0.0.0:8000 6 | volumes: 7 | - .:/usr/src/portal 8 | ports: 9 | - "8000:8000" 10 | links: 11 | - db 12 | environment: 13 | - DJANGO_SETTINGS_MODULE=systers_portal.settings.docker 14 | 15 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Sanket Dasgupta 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Anita-B Portal Docs 2 | 3 | Minimal scaffold for Docusaurus. 4 | 5 | This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator. 6 | 7 | ### Installation 8 | 9 | ``` 10 | $ yarn 11 | ``` 12 | 13 | ### Local Development 14 | 15 | ``` 16 | $ yarn start 17 | ``` 18 | 19 | This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. 20 | 21 | ### Build 22 | 23 | ``` 24 | $ yarn build 25 | ``` 26 | 27 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 28 | 29 | ### Deployment 30 | 31 | ``` 32 | $ GIT_USER= USE_SSH=true yarn deploy 33 | ``` 34 | 35 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 36 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/Home.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: home-page 3 | title: Home Page 4 | --- 5 | 6 | ## Mission 7 | Systers is an international community for all women involved in the technical aspects of computing. We welcome the participation of women technologists of all ages and at any stage of their studies or careers. 8 | 9 | We engage our community by contributing to open source, collaborating with the global community, and learning/enhancing our coding skills. We are committed to providing a safe, positive online community for our many volunteers that offer their skills, time, and commitment to our projects. 10 | 11 | In order to engage with the community, you can sign up on Zulip. 12 | 13 | ## Code of Conduct 14 | A primary goal of Systers is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe, and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). 15 | 16 | See the full [Code of Conduct](https://github.com/systers/portal/blob/master/code_of_conduct.md) and the [Reporting Guidelines](https://github.com/systers/portal/blob/master/reporting_guidelines.md). 17 | 18 | ## Portal 19 | 20 | The Systers Portal is for our communities to post and share information within and with other communities 21 | 22 | To Setup the environment of the project in OSX follow [this](https://github.com/systers/portal/wiki/Installation-Instructions-for-MacOS). 23 | -------------------------------------------------------------------------------- /docs/docs/Test-The-App.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: test-the-app 3 | title: How to Test The Application 4 | --- 5 | 6 | ### The website is served live on [this link](http://ec2-54-215-223-241.us-west-1.compute.amazonaws.com/) 7 | 8 | *** 9 | 10 | ### To run test cases on the app, run the following command after you setup the portal locally. 11 | 12 | ```shell 13 | python systers_portal/manage.py runserver --settings=systers_portal.settings.testing 14 | ``` 15 | ### To check for the PEP8 style guide errors, run the following command 16 | 17 | ```shell 18 | flake8 systers_portal 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/docs/adding-pages.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: adding-pages 3 | title: Adding Pages 4 | --- 5 | 6 | To create a page: 7 | 8 | - Simply add a Markdown file `.md` to `docs/` 9 | - Make sure to add the frontmatter at the top of the page: 10 | 11 | Example: 12 | 13 | ``` 14 | --- 15 | id: adding-pages 16 | title: Adding Pages 17 | --- 18 | ``` 19 | - Finally, to add to the sidebar, add a new entry to `sidebars.js`: 20 | 21 | ``` 22 | { 23 | type: "doc", 24 | id: "adding-pages", 25 | }, 26 | ``` 27 | 28 | Here's the [commit](https://github.com/SanketDG/tinysaurus/commit/f7af055a85d51c26a0175fa23b45c6f58e3a1b5e) that adds this page! 29 | -------------------------------------------------------------------------------- /docs/docs/code-organization.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: code-org 3 | title: Code Organization 4 | --- 5 | 6 | This page will give you a generic overview on Systers Portal project architecture. 7 | As any other Django project Portal organizes its functionality in several apps: 8 | 9 | * **blog** - handles showing, adding, editing and deleting news and resources. 10 | * **common** - generic functionality that can't be part of any other app. 11 | For example, landing, about, contact pages, generic models, helpers, mixins 12 | that are used in several apps. 13 | * **community** - community and subcommunities functionality, like 14 | adding new communities, views and editing community profiles, showing, adding, 15 | editing and deleting community pages, managing permissions regarding each 16 | community. 17 | * **membership** - handles showing, creating, approving and rejecting join 18 | requests to a community, removing and inviting users to become members of a 19 | community. 20 | * **users** - showing and editing user personal profile. 21 | * **meetup** - handles meetup locations and meetup functionality. 22 | 23 | The templates are placed inside ``systers_portal/templates`` folder organized 24 | in a folder structure similar to the apps tree. Respectively the templates 25 | location matches the views location. 26 | 27 | Each app along with models, views, urls and other app related code (admin, forms, 28 | mixins, utils) contains a folder with migrations and tests. The migrations 29 | are generated by Django and shouldn't be edited manually. The tests folder 30 | mimics the app modules covering each module with a separate test file. For 31 | example, tests for ``app_name/models.py`` are contained in the 32 | ``app_name/tests/test_models.py``. 33 | -------------------------------------------------------------------------------- /docs/docs/community-resources.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: community-resources 3 | title: Community Resources 4 | --- 5 | 6 | # Resources and News 7 | 8 | ***Purpose of Need*** 9 | 10 | Now that the community is created and members can be added, the next step which comes up is the need to create resources and news and share them with the members of the community. There could have been a few ways it could have been done precisely: 11 | 12 | * Create an Email Channel and Share information with All Members on the Channel. 13 | * Provide an Option to Create and Add Resources News and Topics on the Portal itself. 14 | 15 | The Second Option was chosen because of the following reasons. 16 | 17 | - To keep the Communities and its operations at one place as it would be easy to manage 18 | - To extend the use of the Portal, so that it would not just be a one time visit to the website. 19 | 20 | ## The Following Features are Provided by the Portal for Sharing Resources and News. 21 | 22 | - Resources can be created by members and moderators 23 | - The Resources which are moderated by the admins or moderators are visible publicly to all the people 24 | - The user can create pages on the community on various topics of discussions. 25 | - Various Tags can be added to resources and news so that people can easily access them. 26 | - News Regarding the Community can be shared on the Community Landing Page 27 | - Discussion Section where people can discuss over particular topics or issues 28 | -------------------------------------------------------------------------------- /docs/docs/database-design.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: database-design 3 | title: Database Design 4 | --- 5 | 6 | # The Database Design for the Portal can be found here 7 | 8 | Before GSoC'20 - https://drive.google.com/file/d/1lV9p4FuUTJ0bz-GPfqH2fpTve2wcavgX/view 9 | 10 | After GSoC'20 - https://app.diagrams.net/#G1XbNpZi0kTGbmW-071ABNEBsMbiq4cYWW 11 | -------------------------------------------------------------------------------- /docs/docs/groups-permissions.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: groups-permissions 3 | title: Groups and Permissions 4 | --- 5 | 6 | Groups and Permissions 7 | ====================== 8 | 9 | As it was previously discussed, each community is associated with 4 auth groups. 10 | Each group implies a specific set of permissions: 11 | 12 | * **Community: Content Contributor** -- a user from this group can: 13 | 14 | * add/change any tags and resource types 15 | * add/change Community news and resources 16 | * **Community: Content Manager** -- a user from this group can do everything a 17 | user from **Community: Content Contributor** can do, plus: 18 | 19 | * delete any tags or resource types 20 | * delete Community news or resources 21 | * add/change/delete a Community page 22 | * approve/delete comments to Community posts (news, resources) 23 | * **Community: User and Content Manager** -- a user from this group can do 24 | everything a user from **Community: Content Manager** can do, plus: 25 | 26 | * add/change/delete members of the Community 27 | * approve Community join requests 28 | * **Community: Community Admin** -- a user from this group can do everything a 29 | user from **Community: User and Content Manager** can do, plus: 30 | 31 | * edit Community profile 32 | -------------------------------------------------------------------------------- /docs/docs/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: intro 3 | title: Introduction 4 | --- 5 | 6 | ## Why? 7 | 8 | Tinysaurus is a minimum scaffold for Docusaurus, with a "no batteries included" approach. 9 | 10 | Mostly, this just removes the landing page and the blog, just keeping the docs for use. 11 | 12 | If you are coming from Github Pages, it's really easy to create a single page 13 | documentation for your project. 14 | 15 | ## Quick Start 16 | 17 | After cloning the repository: 18 | 19 | ### Install all dependencies 20 | 21 | ``` 22 | $ yarn 23 | ``` 24 | 25 | ### Local Development 26 | 27 | ``` 28 | $ yarn start 29 | ``` 30 | 31 | This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. 32 | 33 | ### Build 34 | 35 | ``` 36 | $ yarn build 37 | ``` 38 | 39 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 40 | 41 | ### Deployment 42 | 43 | ``` 44 | $ GIT_USER= USE_SSH=true yarn deploy 45 | -------------------------------------------------------------------------------- /docs/docs/maintainer-guideline.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: maintainer-guideline 3 | title: Maintainer Guidelines 4 | --- 5 | 6 | ## **Guidelines** 7 | 8 | Make sure contributors are respecting the [Contribution Guidelines](https://github.com/anitab-org/portal/blob/develop/CONTRIBUTING.md). If they don't let them know about it: what is missing, what was disrespected? 9 | 10 | ### Code Review 11 | 12 | * When possible get a second opinion before merging. 13 | * Make sure the PR has a link to the issue. 14 | * If you can, let them know why your change request makes sense. 15 | * (nice to have) If you have time, thank them for contributing to the project. 16 | * Make sure that, if the PR changes actual code, to properly test it or wait for someone to confirm the behavior 17 | 18 | ### Merging PRs 19 | **TL;DR:** 20 | 21 | 1. Select "Squash and Merge". 22 | 2. Edit commit if necessary to follow our style guide and leave the PR id in the message. 23 | 24 | **Long version:** 25 | 26 | About merging pull requests (PRs), to keep the project commit history clean, it's important to have all commits from a PR to be squashed when merging. For this you may have to select "Squash and Merge" option: 27 | 28 | 29 | Squash and Merge on GitHub 30 | 31 | If the commit does not follow our [Commit Message Style Guide](https://github.com/anitab-org/portal/wiki/Commit-Message-Style-Guide), make sure to ask the contributor to fix it or you can fix it when merging. 32 | 33 | Edit Commit when Merging 34 | 35 | Make sure to always leave the PR identifier, so that we can traceback a commit to a specific PR. 36 | -------------------------------------------------------------------------------- /docs/docs/meetups-creation.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: meetups-creation 3 | title: Meetups Creation 4 | --- 5 | 6 | The Concept of Meetups 7 | ====================== 8 | 9 | _Meetups were created as a later addition to the original systers portal project because of the need to organise events within the Portal Communities_ 10 | 11 | ***Requesting Meetups*** 12 | 13 | * Any user of the portal i.e registered within the portal can request for meetups 14 | * Meetup Requests require a few fields to be filled in by the users so that the request can be reviewed. They are 15 | * Title for Meetup 16 | * Venue 17 | * Location 18 | * Date 19 | * Time 20 | * Description 21 | * Resources and Images 22 | * The Request is then reviewed by the staff and will be accepted or rejected. 23 | * If accepted, the user who requested for the meetup to be created will become the leader for the meetup 24 | * The Leader can edit meetup details and approve or reject support requests. 25 | 26 | ***Support Requests*** 27 | 28 | * Meetups may require support from volunteers for perfect organisation of any meetup 29 | * Users can checkout the details for a particular meetup and then add a support request. 30 | * The request would be approved or rejected by the leader. 31 | -------------------------------------------------------------------------------- /docs/docs/tech-stack.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: tech-stack 3 | title: Tech Stack 4 | --- 5 | 6 | 7 | # Tech Stack Used in Portal 8 | 9 | ### Language - **Python 3.x** 10 | 11 | ### Framework - **Django 3.04** 12 | 13 | *** 14 | 15 | ## Extentions - 16 | 17 | **Pillow** - For image handling 18 | 19 | **[Django Nose](https://django-nose.readthedocs.io/en/latest/)** - For Testing 20 | 21 | **[Django-Guardian](https://django-guardian.readthedocs.io/)** - For Handling Permissions 22 | 23 | **[Cities-Light](https://django-cities-light.readthedocs.io/)** - Cities and Their Location 24 | 25 | **[Crispy Forms](https://django-crispy-forms.readthedocs.io/)** - Making the Django Forms More intuitive 26 | 27 | **[Django--allauth](https://django-allauth.readthedocs.io/en/latest/)** - Authentication 28 | 29 | **[GeoIp2](https://geoip2.readthedocs.io/)** - Fetching User Locations 30 | 31 | **[Django-Ipware](https://github.com/un33k/django-ipware)** - Fetching IP for Users from their Mete Headers 32 | 33 | **[Natural Language Toolkit](http://www.nltk.org/)** - For Stopwords and Tokenizing Text Data 34 | 35 | **[Gensim](https://radimrehurek.com/gensim/)** - Creating Similarity Objects and TF-IDF to get similarity score 36 | 37 | **[Django-MultiUpload](https://github.com/Chive/django-multiupload)** - To support multiple image upload in django 38 | 39 | **[pyJWT](https://pyjwt.readthedocs.io/en/stable/)** - Create JWT tokens for Zoom API Authentication 40 | 41 | **[Python APScheduler](https://apscheduler.readthedocs.io/)** - For Async Tasks and Scheduled Emails 42 | -------------------------------------------------------------------------------- /docs/docs/zoom-meetings.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: zoom-meetings 3 | title: Zoom Meetings 4 | --- 5 | 6 | 7 | # Zoom Virtual Meetings 8 | 9 | _The pandemic situation made the portal team realize that there was a need to have virtual meetings enabled so that the portal users could hold meetings even in the lockdown situation._ 10 | 11 | Here are the Details regarding the implementation of the Virtual Meetings using Zoom API: 12 | 13 | ### Authorization 14 | 15 | Every API requires authorization from the user. This is the API Authorization Process for ZOOM API using JWT: 16 | 17 | - The API KEY for the App is signed to the payload of the jwt, along with the expiration time of the JWT; 18 | - The expiration time for the jwt in the portal is being set to 30 seconds 19 | - Using pyjwt the jwt is encoded using the payload, after being signed by the SECRET KEY. 20 | - Once the JWT is created we can use this in the authorization header and make calls to the API. 21 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: "Portal", 3 | tagline: "Minimal scaffold for Docusaurus", 4 | url: "https://your-docusaurus-test-site.com", 5 | baseUrl: "/", 6 | onBrokenLinks: "throw", 7 | favicon: "img/favicon.ico", 8 | organizationName: "anitab-org", // Usually your GitHub org/user name. 9 | projectName: "portal", // Usually your repo name. 10 | themeConfig: { 11 | colorMode: { 12 | defaultMode: "dark", 13 | }, 14 | navbar: { 15 | title: "AnitaB Portal Docs", 16 | logo: { 17 | alt: "My Site Logo", 18 | src: "img/logo.svg", 19 | }, 20 | items: [ 21 | { 22 | href: "https://anitab.org/", 23 | label: "Anita-B.org", 24 | position: "right", 25 | }, 26 | { 27 | href: "https://github.com/anitab-org/portal", 28 | label: "GitHub", 29 | position: "right", 30 | }, 31 | ], 32 | }, 33 | footer: { 34 | style: "dark", 35 | copyright: `Copyright © ${new Date().getFullYear()} Anita-B. Built with Docusaurus.`, 36 | }, 37 | }, 38 | presets: [ 39 | [ 40 | "@docusaurus/preset-classic", 41 | { 42 | docs: { 43 | routeBasePath: "/", 44 | // It is recommended to set document id as docs home page (`docs/` path). 45 | homePageId: "home-page", 46 | sidebarPath: require.resolve("./sidebars.js"), 47 | // Please change this to your repo. 48 | editUrl: "https://github.com/anitab-org/portal/edit/master/docs/", 49 | }, 50 | theme: { 51 | customCss: require.resolve("./src/css/custom.css"), 52 | }, 53 | }, 54 | ], 55 | ], 56 | }; 57 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "portal", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "serve": "docusaurus serve" 12 | }, 13 | "dependencies": { 14 | "@docusaurus/core": "^2.0.0-alpha.61", 15 | "@docusaurus/preset-classic": "^2.0.0-alpha.61", 16 | "@mdx-js/react": "^1.5.8", 17 | "clsx": "^1.1.1", 18 | "react": "^16.8.4", 19 | "react-dom": "^16.8.4" 20 | }, 21 | "browserslist": { 22 | "production": [ 23 | ">0.2%", 24 | "not dead", 25 | "not op_mini all" 26 | ], 27 | "development": [ 28 | "last 1 chrome version", 29 | "last 1 firefox version", 30 | "last 1 safari version" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | docs: [ 3 | { 4 | type: "doc", 5 | id: "home-page", 6 | }, 7 | { 8 | type: "category", 9 | label: "How To Contribute", 10 | items: ['how-to-contribute', 'commit-message-style-guide', 'test-the-app', 'maintainer-guideline',], 11 | }, 12 | { 13 | type: "link", 14 | label: "Project Requirements", 15 | href: "https://docs.google.com/document/d/1LCGhXPDmwivQGG0EYeJaMlEzBsK9Tp-kOvX9aBTSdqY/edit", 16 | }, 17 | { 18 | type: "category", 19 | label: "Setup & Install", 20 | items: ['setup-osx', 'environment-variables',], 21 | }, 22 | { 23 | type: "link", 24 | label: "User Interface Design", 25 | href: "https://docs.google.com/document/d/1wNdZ_jNAuSNa_E6i9kXGY5i-eVMpCgDqm6R8-2w6bq0/edit?usp=sharing", 26 | }, 27 | { 28 | type: "category", 29 | label: "Development", 30 | items: ['tech-stack', 'coding-standard', 'code-org', 'user-auth', 'tech-decisions',], 31 | }, 32 | { 33 | type: "doc", 34 | id: "database-design", 35 | }, 36 | { 37 | type: "category", 38 | label: "Main Concepts", 39 | items: ['community-creation', 'community-resources', 'meetups-creation', 'groups-permissions', 'zoom-meetings',], 40 | }, 41 | { 42 | type:"doc", 43 | id: "future-ideas", 44 | }, 45 | { 46 | type: "category", 47 | label: "Contribution to the Docs", 48 | items: ['intro', 'adding-pages',], 49 | }, 50 | ], 51 | }; 52 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #25c2a0; 11 | --ifm-color-primary-dark: rgb(33, 175, 144); 12 | --ifm-color-primary-darker: rgb(31, 165, 136); 13 | --ifm-color-primary-darkest: rgb(26, 136, 112); 14 | --ifm-color-primary-light: rgb(70, 203, 174); 15 | --ifm-color-primary-lighter: rgb(102, 212, 189); 16 | --ifm-color-primary-lightest: rgb(146, 224, 208); 17 | --ifm-code-font-size: 95%; 18 | } 19 | 20 | .docusaurus-highlight-code-line { 21 | background-color: rgb(72, 77, 91); 22 | display: block; 23 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 24 | padding: 0 var(--ifm-pre-padding); 25 | } 26 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = systers_portal.settings.testing -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | -r prod.txt 2 | Sphinx==3.4.2 3 | coverage==5.3 4 | django-nose==1.4.6 5 | flake8==3.8.4 6 | sphinx-rtd-theme==0.5.0 7 | pylint==2.6.0 8 | pytest==6.1.2 9 | pytest-cov==2.11.1 10 | pytest-django==4.1.0 11 | pytest-pylint==0.18.0 12 | python-decouple==3.4 13 | selenium==3.141.0 14 | djangorestframework==3.12.2 15 | geopy==2.0.0 16 | pinax-notifications==6.0.0 17 | -------------------------------------------------------------------------------- /requirements/prod.txt: -------------------------------------------------------------------------------- 1 | Django==3.1.3 2 | Pillow==8.0.1 3 | django-allauth==0.44.0 4 | django-braces==1.14.0 5 | django-cities-light==3.8.1 6 | django-ckeditor==6.0.0 7 | django-crispy-forms==1.10.0 8 | django-guardian==2.3.0 9 | django-imagekit==4.0.2 10 | psycopg2==2.8.6 11 | python3-openid==3.2.0 12 | geoip2==4.1.0 13 | django-ipware==3.0.2 14 | django-apscheduler==0.5.1 15 | git+https://github.com/Chive/django-multiupload.git#egg=django-multiupload 16 | nltk==3.5 17 | gensim==3.8.3 18 | pyjwt==2.0.0 19 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = F403, F405 3 | exclude = .git,docs/*,systers_portal/manage.py,wsgi.py,migrations 4 | max-line-length = 100 5 | -------------------------------------------------------------------------------- /systers_portal/GeoLite2-City_20200616/COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | Database and Contents Copyright (c) 2020 MaxMind, Inc. 2 | -------------------------------------------------------------------------------- /systers_portal/GeoLite2-City_20200616/GeoLite2-City.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/GeoLite2-City_20200616/GeoLite2-City.mmdb -------------------------------------------------------------------------------- /systers_portal/GeoLite2-City_20200616/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Use of this MaxMind product is governed by MaxMind's GeoLite2 End User License Agreement, which can be viewed at https://www.maxmind.com/en/geolite2/eula. 2 | 3 | This database incorporates GeoNames [https://www.geonames.org] geographical data, which is made available under the Creative Commons Attribution 4.0 License. To view a copy of this license, visit https://creativecommons.org/licenses/by/4.0/. 4 | -------------------------------------------------------------------------------- /systers_portal/GeoLite2-City_20200616/README.txt: -------------------------------------------------------------------------------- 1 | Latitude and longitude are not precise and should not be used to identify a particular street address or household. 2 | -------------------------------------------------------------------------------- /systers_portal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/__init__.py -------------------------------------------------------------------------------- /systers_portal/blog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/blog/__init__.py -------------------------------------------------------------------------------- /systers_portal/blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from blog.models import (Tag, ResourceType, News, Resource, UserPins) 4 | 5 | 6 | admin.site.register(Tag) 7 | admin.site.register(ResourceType) 8 | admin.site.register(News) 9 | admin.site.register(Resource) 10 | admin.site.register(UserPins) 11 | -------------------------------------------------------------------------------- /systers_portal/blog/migrations/0003_userpins.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.9 on 2020-08-10 06:08 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0001_initial'), 11 | ('blog', '0002_auto_20200724_2045'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='UserPins', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('pins', models.ManyToManyField(blank=True, related_name='pins', to='blog.Resource', verbose_name='pins')), 20 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='users.SystersUser')), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /systers_portal/blog/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/blog/migrations/__init__.py -------------------------------------------------------------------------------- /systers_portal/blog/mixins.py: -------------------------------------------------------------------------------- 1 | from blog.models import ResourceType 2 | 3 | 4 | class ResourceTypesMixin(object): 5 | """Mixin allows to add to the context all resource type object""" 6 | def get_context_data(self, **kwargs): 7 | context = super(ResourceTypesMixin, self).get_context_data(**kwargs) 8 | context["resource_types"] = ResourceType.objects.all() 9 | return context 10 | -------------------------------------------------------------------------------- /systers_portal/blog/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/blog/tests/__init__.py -------------------------------------------------------------------------------- /systers_portal/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/common/__init__.py -------------------------------------------------------------------------------- /systers_portal/common/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from common.models import Comment 3 | 4 | 5 | admin.site.register(Comment) 6 | -------------------------------------------------------------------------------- /systers_portal/common/forms.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ImproperlyConfigured 2 | from django.forms import ModelForm 3 | 4 | 5 | class ModelFormWithHelper(ModelForm): 6 | """Custom ModelForm that allows to attach a crispy-forms FormHelper class, 7 | that will modify in some way the rendering of the layout. 8 | 9 | Example:: 10 | 11 | FooForm(ModelFormWithHelper): 12 | class Meta: 13 | model = FooModel 14 | helper_class = FooFormHelper 15 | """ 16 | def __init__(self, *args, **kwargs): 17 | super(ModelFormWithHelper, self).__init__(*args, **kwargs) 18 | 19 | if hasattr(self.Meta, "helper_class"): 20 | helper_class = getattr(self.Meta, "helper_class") 21 | kwargs = self.get_helper_kwargs() 22 | self.helper = helper_class(self, **kwargs) 23 | else: 24 | raise ImproperlyConfigured( 25 | "{0} is missing a 'helper_class' meta attribute.".format( 26 | self.__class__.__name__)) 27 | 28 | def get_helper_kwargs(self): 29 | """Get all helper attributes from class Meta by stripping them of 30 | `helper_` part of attribute string 31 | 32 | :return: dict with helper kwargs 33 | """ 34 | kwargs = {} 35 | for attr, value in self.Meta.__dict__.items(): 36 | if attr.startswith("helper_") and attr != "helper_class": 37 | new_attr = attr.split("_", 1)[1] 38 | kwargs[new_attr] = value 39 | return kwargs 40 | -------------------------------------------------------------------------------- /systers_portal/common/helpers.py: -------------------------------------------------------------------------------- 1 | from crispy_forms.bootstrap import FormActions 2 | from crispy_forms.helper import FormHelper, Layout 3 | from crispy_forms.layout import Submit, HTML 4 | 5 | 6 | class SubmitCancelFormHelper(FormHelper): 7 | """Custom FormHelper that appends to the layout cancel and submit 8 | buttons (works only with bootstrap crispy-forms pack). It expects a 9 | `cancel_href` attribute to be used as cancel button URL. 10 | 11 | Example:: 12 | 13 | SubmitCancelFormHelper(self, cancel_href="/some/url/") 14 | """ 15 | def __init__(self, *args, **kwargs): 16 | cancel_href = kwargs.pop('cancel_href', '#') 17 | super(SubmitCancelFormHelper, self).__init__(*args, **kwargs) 18 | self.layout.append( 19 | Layout( 20 | FormActions( 21 | HTML("""Cancel""".format(cancel_href)), 23 | Submit('save', 'Submit'), 24 | ) 25 | ) 26 | ) 27 | -------------------------------------------------------------------------------- /systers_portal/common/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.8 on 2020-07-24 20:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Comment', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('date_created', models.DateField(auto_now_add=True, verbose_name='Date created')), 19 | ('is_approved', models.BooleanField(default=True, verbose_name='Is approved')), 20 | ('body', models.TextField(verbose_name='Body')), 21 | ('object_id', models.PositiveIntegerField()), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /systers_portal/common/migrations/0002_auto_20200724_2045.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.8 on 2020-07-24 20:45 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('contenttypes', '0002_remove_content_type_name'), 13 | ('users', '0001_initial'), 14 | ('common', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='comment', 20 | name='author', 21 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.SystersUser', verbose_name='Author'), 22 | ), 23 | migrations.AddField( 24 | model_name='comment', 25 | name='content_type', 26 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /systers_portal/common/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'ana' 2 | -------------------------------------------------------------------------------- /systers_portal/common/mixins.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import ImproperlyConfigured 2 | 3 | from users.models import SystersUser 4 | 5 | 6 | class UserDetailsMixin(object): 7 | """Mixin allows to add to the context information about the request user: 8 | 9 | * Is user member of the community 10 | * User join request to the community 11 | """ 12 | community = None 13 | 14 | def get_context_data(self, **kwargs): 15 | context = super(UserDetailsMixin, self).get_context_data(**kwargs) 16 | user = self.request.user 17 | if user.username: 18 | community = self.get_community() 19 | systers_user = SystersUser.objects.get(user=user) 20 | context['is_member'] = systers_user.is_member(community) 21 | context['join_request'] = systers_user.get_last_join_request( 22 | community) 23 | return context 24 | 25 | def get_community(self): 26 | """Get a Community object. 27 | 28 | :return: Community object 29 | :raises ImproperlyConfigured: if Community is set to None 30 | """ 31 | if self.community is None: 32 | raise ImproperlyConfigured( 33 | '{0} is missing a community property. Define {0}.community ' 34 | 'or override {0}.get_community()' 35 | .format(self.__class__.__name__) 36 | ) 37 | return self.community 38 | -------------------------------------------------------------------------------- /systers_portal/common/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/common/templatetags/__init__.py -------------------------------------------------------------------------------- /systers_portal/common/templatetags/verbose_name.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.simple_tag 8 | def verbose_name(instance, field_name): 9 | """Returns the verbose name of a model field 10 | 11 | :param instance: model class instance 12 | :param field_name: string model field name 13 | :returns: string verbose name of the field name 14 | """ 15 | return instance._meta.get_field(field_name).verbose_name 16 | -------------------------------------------------------------------------------- /systers_portal/common/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/common/tests/__init__.py -------------------------------------------------------------------------------- /systers_portal/common/tests/selenium/credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "username":"foo", 3 | "password":"foobar" 4 | } 5 | -------------------------------------------------------------------------------- /systers_portal/common/tests/selenium/test_can_goto_common_pages.py: -------------------------------------------------------------------------------- 1 | from common.tests.selenium.base import SeleniumTestCase 2 | 3 | 4 | class TestCanGoToCommonPages(SeleniumTestCase): 5 | """ 6 | Automated visual tests to visit common pages 7 | """ 8 | 9 | def test_can_goto_home_page(self): 10 | self.browser.get(self.live_server_url) 11 | welcome = self.browser.find_element_by_tag_name('h1').text 12 | self.assertTrue(welcome == 'Welcome to Systers Portal!') 13 | 14 | def test_can_goto_about_page(self): 15 | self.browser.get('{}{}'.format(self.live_server_url, '/about-us')) 16 | about = self.browser.find_element_by_tag_name('h1').text 17 | self.assertTrue(about == 'About Systers') 18 | 19 | def test_can_goto_contact_page(self): 20 | self.browser.get('{}{}'.format(self.live_server_url, '/contact')) 21 | contact = self.browser.find_element_by_tag_name('h1').text 22 | self.assertTrue(contact == 'Contact Systers') 23 | 24 | def test_can_goto_login_page(self): 25 | self.browser.get('{}{}'.format(self.live_server_url, '/accounts/login/')) 26 | self.assertTrue('Sign In' in self.browser.title) 27 | 28 | def test_can_goto_signup_page(self): 29 | self.browser.get('{}{}'.format(self.live_server_url, '/accounts/signup/')) 30 | self.assertTrue('Sign Up' in self.browser.title) 31 | -------------------------------------------------------------------------------- /systers_portal/common/tests/test_forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.core.exceptions import ImproperlyConfigured 3 | from django.test import TestCase 4 | from crispy_forms.helper import FormHelper 5 | 6 | from common.forms import ModelFormWithHelper 7 | from common.helpers import SubmitCancelFormHelper 8 | 9 | 10 | class ModelFormWithHelperTestCase(TestCase): 11 | def test_model_form_without_helper(self): 12 | """Test the custom model form without a helper class""" 13 | class FooForm(ModelFormWithHelper): 14 | class Meta: 15 | model = User 16 | fields = "__all__" 17 | 18 | self.assertRaises(ImproperlyConfigured, FooForm, {}) 19 | 20 | def test_model_form_with_helper(self): 21 | """Test the custom model form with a helper class present""" 22 | class FooForm(ModelFormWithHelper): 23 | class Meta: 24 | model = User 25 | fields = "__all__" 26 | helper_class = FormHelper 27 | 28 | form = FooForm() 29 | self.assertEqual(form.helper.__class__, FormHelper) 30 | 31 | class BarForm(ModelFormWithHelper): 32 | class Meta: 33 | model = User 34 | fields = "__all__" 35 | helper_class = SubmitCancelFormHelper 36 | helper_cancel_href = "some/url/" 37 | 38 | form = BarForm() 39 | self.assertEqual(form.helper.__class__, SubmitCancelFormHelper) 40 | -------------------------------------------------------------------------------- /systers_portal/common/tests/test_helpers.py: -------------------------------------------------------------------------------- 1 | from django.template import Context, Engine 2 | from django.test import TestCase 3 | from crispy_forms.tests.forms import SampleForm 4 | from common.helpers import SubmitCancelFormHelper 5 | 6 | 7 | class SubmitCancelFormHelperTestCase(TestCase): 8 | 9 | def test_submit_cancel_form_helper(self): 10 | """Test custom crispy forms layout helper""" 11 | template = Engine().get_default().from_string(""" 12 | {% load crispy_forms_tags %} 13 | {% crispy form %} 14 | """) 15 | 16 | test_form = SampleForm() 17 | test_form.helper = SubmitCancelFormHelper(test_form) 18 | 19 | c = Context({'form': test_form}) 20 | 21 | html = template.render(c) 22 | self.assertEqual(html.count('role="button"'), 1) 23 | self.assertEqual(html.count('href="#"'), 1) 24 | self.assertEqual(html.count('Cancel'), 1) 25 | self.assertEqual(html.count('Submit'), 1) 26 | 27 | test_form = SampleForm() 28 | test_form.helper = SubmitCancelFormHelper(test_form, 29 | cancel_href="/some/url/") 30 | 31 | c = Context({'form': test_form}) 32 | 33 | html = template.render(c) 34 | self.assertEqual(html.count('href="/some/url/'), 1) 35 | -------------------------------------------------------------------------------- /systers_portal/common/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from cities_light.models import Country, City 2 | from django.test import TestCase 3 | from django.contrib.auth.models import User 4 | from django.contrib.contenttypes.models import ContentType 5 | 6 | from blog.models import News 7 | from common.models import Comment 8 | from community.models import Community 9 | from users.models import SystersUser 10 | 11 | 12 | class CommentModelTestCase(TestCase): 13 | def setUp(self): 14 | self.user = User.objects.create(username='foo', password='foobar') 15 | self.systers_user = SystersUser.objects.get(user=self.user) 16 | country = Country.objects.create(name='Bar', continent='AS') 17 | location = City.objects.create(name='Foo', display_name='Foo', 18 | country=country) 19 | self.community = Community.objects.create(name="Foo", slug="foo", 20 | order=1, location=location, 21 | admin=self.systers_user) 22 | 23 | def test_str(self): 24 | """Test Comment object str/unicode representation""" 25 | news = News.objects.create(slug="foonews", title="Bar", 26 | author=self.systers_user, 27 | content="Hi there!", 28 | community=self.community) 29 | related_object_type = ContentType.objects.get_for_model(news) 30 | comment = Comment.objects.create(author=self.systers_user, body="Bar", 31 | object_id=news.id, 32 | content_type=related_object_type) 33 | self.assertEqual(str(comment), 34 | "Comment by foo to Bar of Foo Community") 35 | -------------------------------------------------------------------------------- /systers_portal/common/tests/test_templatetags.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from common.templatetags.verbose_name import verbose_name 4 | from users.models import SystersUser 5 | 6 | 7 | class TemplateTagsTestCase(TestCase): 8 | def test_verbose_names(self): 9 | """Test verbose_name template tag""" 10 | self.assertEqual(verbose_name(SystersUser, "homepage_url"), "Homepage") 11 | -------------------------------------------------------------------------------- /systers_portal/common/tests/test_views.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | from django.test import TestCase, Client 3 | from django.contrib.auth.models import User 4 | 5 | 6 | class CommonViewsTestCase(TestCase): 7 | def setUp(self): 8 | self.client = Client() 9 | 10 | def test_index_page(self): 11 | """Test index/landing page""" 12 | index_url = reverse('index') 13 | response = self.client.get(index_url) 14 | self.assertEqual(response.status_code, 200) 15 | self.assertTemplateUsed(response, 'common/index.html') 16 | 17 | def test_contact_page(self): 18 | """Test contact page""" 19 | contact_url = reverse('contact') 20 | response = self.client.get(contact_url) 21 | self.assertEqual(response.status_code, 200) 22 | self.assertTemplateUsed(response, 'common/contact.html') 23 | 24 | def test_about_us_page(self): 25 | """Test about us page""" 26 | about_us_url = reverse('about-us') 27 | response = self.client.get(about_us_url) 28 | self.assertEqual(response.status_code, 200) 29 | self.assertTemplateUsed(response, 'common/about_us.html') 30 | 31 | def test_login_logout(self): 32 | """Test Login and logout functionality""" 33 | # Create a temporary user 34 | u = User.objects.create_user('admin', 'admin@admin.com', 'Qwerty@123') 35 | # Check response code on get request for login 36 | response = self.client.get(reverse('account_login')) 37 | self.assertEqual(response.status_code, 200) 38 | # Log in the user 39 | self.client.login(username=u.username, password=u.password) 40 | # Check response code for logout 41 | response = self.client.get(reverse('logout')) 42 | self.assertEqual(response.status_code, 302) 43 | # Log out the user 44 | self.client.logout() 45 | -------------------------------------------------------------------------------- /systers_portal/common/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import TemplateView 2 | from django.views.decorators.cache import cache_control 3 | from allauth.account.views import LogoutView 4 | 5 | 6 | class IndexView(TemplateView): 7 | template_name = "common/index.html" 8 | 9 | 10 | class ContactView(TemplateView): 11 | template_name = "common/contact.html" 12 | 13 | 14 | class AboutUsView(TemplateView): 15 | template_name = "common/about_us.html" 16 | 17 | 18 | class NewCommunityProposalView(TemplateView): 19 | template_name = "common/new_community_proposal.html" 20 | 21 | 22 | class Logout(LogoutView): 23 | 24 | @cache_control(no_cache=True, must_revalidate=True, no_store=True) 25 | def get(self, *args, **kwargs): 26 | return super().get(*args, **kwargs) 27 | 28 | def post(self, *args, **kwargs): 29 | return super().post(*args, **kwargs) 30 | -------------------------------------------------------------------------------- /systers_portal/community/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'community.apps.CommunityConfig' 2 | -------------------------------------------------------------------------------- /systers_portal/community/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from guardian.admin import GuardedModelAdmin 3 | 4 | from community.models import Community, CommunityPage, RequestCommunity 5 | 6 | 7 | class CommunityAdmin(GuardedModelAdmin): 8 | def save_model(self, request, obj, form, change): 9 | """Override this method in order to be able to add the community admin 10 | to members list via admin panel.""" 11 | members = list(form.cleaned_data['members']) 12 | members.extend([obj.admin]) 13 | form.cleaned_data['members'] = members 14 | super(CommunityAdmin, self).save_model(request, obj, form, change) 15 | 16 | 17 | admin.site.register(RequestCommunity) 18 | admin.site.register(Community, CommunityAdmin) 19 | admin.site.register(CommunityPage) 20 | -------------------------------------------------------------------------------- /systers_portal/community/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommunityConfig(AppConfig): 5 | name = 'community' 6 | 7 | def ready(self): 8 | import community.signals # noqa # pylint: disable=unused-variable 9 | -------------------------------------------------------------------------------- /systers_portal/community/constants.py: -------------------------------------------------------------------------------- 1 | # user groups 2 | CONTENT_CONTRIBUTOR = "{0}: Content Contributor" 3 | CONTENT_MANAGER = "{0}: Content Manager" 4 | USER_CONTENT_MANAGER = "{0}: User and Content Manager" 5 | COMMUNITY_ADMIN = "{0}: Community Admin" 6 | 7 | # community 8 | DEFAULT_COMMUNITY_ACTIVE_PAGE = 'news' 9 | 10 | COMMUNITY_PRESENCE_CHOICES = [ 11 | ('Facebook Page', 'Facebook Page'), 12 | ('Facebook Group', 'Facebook Group'), 13 | ('Twitter', 'Twitter'), 14 | ('Instagram', 'Instagram'), 15 | ('Other', 'Other') 16 | ] 17 | 18 | COMMUNITY_TYPES_CHOICES = [ 19 | ('Affinity Group', 'Affinity Group (Latinas in Computing, LGBT, etc)'), 20 | ('Special Interest Group', 21 | 'Special Interest Group (Student Researchers, Systers in Government,' 22 | 'Women in Cyber Security, etc) '), 23 | ('Email list', 'Email list (using Mailman3)'), 24 | ('Other', 'Other') 25 | ] 26 | 27 | COMMUNITY_CHANNEL_CHOICES = [ 28 | ('Existing Social Media Channels ', 'Existing Social Media Channels '), 29 | ('Request New Social Media Channels ', 30 | 'Request New Social Media Channels '), 31 | ] 32 | 33 | YES_NO_CHOICES = [ 34 | ('Yes', 'Yes'), 35 | ('No', 'No') 36 | ] 37 | 38 | # STATUS values for RequestCommunity 39 | ORDER_NULL = "order_null" 40 | SLUG_ALREADY_EXISTS = "slug_already_exists" 41 | ORDER_ALREADY_EXISTS = "order_already_exists" 42 | OK = "success" 43 | 44 | # messages for the approval of a community request 45 | ORDER_NULL_MSG = "Order is not set, Please choose an order." 46 | ORDER_ALREADY_EXISTS_MSG = "Order {0} already exists, please choose an order other than {1}." 47 | SLUG_ALREADY_EXISTS_MSG = "Slug {0} already exists, please choose a slug other than {1}." 48 | SUCCESS_MSG = "Community created successfully!" 49 | -------------------------------------------------------------------------------- /systers_portal/community/context_processors.py: -------------------------------------------------------------------------------- 1 | from community.models import Community 2 | 3 | 4 | def communities_processor(request): 5 | """Custom template context preprocessor that allows to inject into every 6 | request the list of all communities. This is necessary in order to display 7 | the list of communities in the navigation bar.""" 8 | communities = Community.objects.all().order_by("order") 9 | return {'communities': communities} 10 | -------------------------------------------------------------------------------- /systers_portal/community/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/community/migrations/__init__.py -------------------------------------------------------------------------------- /systers_portal/community/permissions.py: -------------------------------------------------------------------------------- 1 | from community.constants import * 2 | 3 | 4 | groups_templates = {"content_contributor": CONTENT_CONTRIBUTOR, 5 | "content_manager": CONTENT_MANAGER, 6 | "user_content_manager": USER_CONTENT_MANAGER, 7 | "community_admin": COMMUNITY_ADMIN} 8 | 9 | content_contributor_permissions = [ 10 | "add_tag", 11 | "change_tag", 12 | "add_resourcetype", 13 | "change_resourcetype", 14 | "add_community_news", 15 | "change_community_news", 16 | "add_community_resource", 17 | "change_community_resource", 18 | ] 19 | 20 | content_manager_permissions = content_contributor_permissions + [ 21 | "delete_tag", 22 | "delete_resourcetype", 23 | "delete_community_news", 24 | "delete_community_resource", 25 | "add_community_page", 26 | "change_community_page", 27 | "delete_community_page", 28 | "approve_community_comment", 29 | "delete_community_comment", 30 | ] 31 | 32 | user_content_manager_permissions = content_manager_permissions + [ 33 | "add_community_systersuser", 34 | "change_community_systersuser", 35 | "delete_community_systersuser", 36 | "approve_community_joinrequest" 37 | ] 38 | 39 | community_admin_permissions = user_content_manager_permissions + [ 40 | "change_community", 41 | "add_community" 42 | ] 43 | 44 | group_permissions = { 45 | "content_contributor": content_contributor_permissions, 46 | "content_manager": content_manager_permissions, 47 | "user_content_manager": user_content_manager_permissions, 48 | "community_admin": community_admin_permissions 49 | } 50 | -------------------------------------------------------------------------------- /systers_portal/community/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/community/tests/__init__.py -------------------------------------------------------------------------------- /systers_portal/community/tests/selenium/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from selenium import webdriver 4 | 5 | browsers = { 6 | 'firefox': webdriver.Firefox, 7 | 'chrome': webdriver.Chrome, 8 | } 9 | 10 | 11 | @pytest.fixture(scope="class") 12 | def browser(request): 13 | if 'DISPLAY' not in os.environ: 14 | pytest.skip('Test requires display server (export DISPLAY)') 15 | 16 | b = browsers[request.config.getoption("--browser-config")]() 17 | 18 | request.addfinalizer(lambda *args: b.quit()) 19 | 20 | # inject class variables 21 | request.cls.browser = b 22 | 23 | return b 24 | 25 | 26 | def pytest_addoption(parser): 27 | parser.addoption( 28 | "--browser-config", 29 | action="store", 30 | default="chrome", 31 | help="firefox, chrome are allowed parameters") 32 | -------------------------------------------------------------------------------- /systers_portal/community/tests/selenium/test_community_page.py: -------------------------------------------------------------------------------- 1 | from common.tests.selenium.base import SeleniumTestCase 2 | from community.models import Community 3 | 4 | 5 | class TestCommunityPage(SeleniumTestCase): 6 | """ 7 | Automated visual tests for user actions on community page 8 | """ 9 | 10 | def setUp(self): 11 | super().setUp() 12 | self.community = Community.objects.create( 13 | name="Foo Community", 14 | slug="foo-community", 15 | order=1, 16 | admin=self.systers_user) 17 | 18 | def test_can_goto_community_page(self): 19 | self.browser.get('{}{}'.format(self.live_server_url, 20 | '/community/foo-community')) 21 | title = self.browser.find_element_by_xpath( 22 | "//h1[contains(text(),'Foo Community')]").text 23 | self.assertTrue('Foo Community' == title) 24 | 25 | def test_can_click_request_community(self): 26 | self.browser.get(self.live_server_url) 27 | self.browser.add_cookie(self.create_session_cookie()) 28 | self.browser.refresh() 29 | self.browser.get(self.live_server_url) 30 | dropdown = self.browser.find_element_by_id('community-dropdown') 31 | dropdown.click() 32 | menu = dropdown.find_element_by_class_name('dropdown-menu') 33 | for menu_item in menu.find_elements_by_tag_name('li'): 34 | if menu_item.text == 'REQUEST A NEW COMMUNITY': 35 | menu_item.click() 36 | break 37 | self.assertTrue('Request a new Community' in self.browser.title) 38 | -------------------------------------------------------------------------------- /systers_portal/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", 7 | "systers_portal.settings.dev") 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /systers_portal/meetup/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'meetup.apps.MeetupConfig' 2 | -------------------------------------------------------------------------------- /systers_portal/meetup/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from meetup.models import (Meetup, Rsvp, SupportRequest, RequestMeetup) 4 | 5 | from meetup.models import MeetupImages 6 | 7 | admin.site.register(Meetup) 8 | admin.site.register(Rsvp) 9 | admin.site.register(SupportRequest) 10 | admin.site.register(RequestMeetup) 11 | admin.site.register(MeetupImages) 12 | -------------------------------------------------------------------------------- /systers_portal/meetup/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MeetupConfig(AppConfig): 5 | name = 'meetup' 6 | 7 | def ready(self): 8 | import meetup.signals # noqa # pylint: disable=unused-variable 9 | -------------------------------------------------------------------------------- /systers_portal/meetup/constants.py: -------------------------------------------------------------------------------- 1 | # user groups 2 | COMMUNITY_MEMBER = "{0}: Community Member" 3 | COMMUNITY_MODERATOR = "{0}: Community Moderator" 4 | COMMUNITY_LEADER = "{0}: Community Leader" 5 | 6 | # STATUS constants 7 | LOCATION_ALREADY_EXISTS = "location_already_exists" 8 | SLUG_ALREADY_EXISTS = "slug_already_exists" 9 | NAME_ALREADY_EXISTS = "name_already_exists" 10 | OK = "success" 11 | 12 | # messages for the approval of a meetup location request 13 | LOCATION_ALREADY_EXISTS_MSG = "A Meetup Location at this location {0} exists." 14 | SLUG_ALREADY_EXISTS_MSG = "Slug {0} already exists, please choose a different slug." 15 | NAME_ALREADY_EXISTS_MSG = "Name {0} already exists, please choose a different name." 16 | SUCCESS_MSG = "Meetup Location created successfully!" 17 | ERROR_MSG = "Something went wrong. Please try again" 18 | 19 | # messages for the approval of a meetup request 20 | SUCCESS_MEETUP_MSG = "Meetup sucessfully created!" 21 | -------------------------------------------------------------------------------- /systers_portal/meetup/migrations/0002_auto_20200804_0509.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.9 on 2020-08-04 05:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('meetup', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='meetup', 15 | name='is_virtual', 16 | field=models.BooleanField(default=False), 17 | ), 18 | migrations.AddField( 19 | model_name='meetup', 20 | name='meet_link', 21 | field=models.URLField(blank=True, null=True, verbose_name='Link for the Meeting'), 22 | ), 23 | migrations.AddField( 24 | model_name='requestmeetup', 25 | name='is_virtual', 26 | field=models.BooleanField(default=False), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /systers_portal/meetup/migrations/0003_auto_20200804_0527.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.9 on 2020-08-04 05:27 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('cities_light', '0009_add_subregion'), 11 | ('meetup', '0002_auto_20200804_0509'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='meetup', 17 | name='meetup_location', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cities_light.City', verbose_name='Meetup Location'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /systers_portal/meetup/migrations/0004_auto_20200804_0538.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.9 on 2020-08-04 05:38 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('cities_light', '0009_add_subregion'), 11 | ('meetup', '0003_auto_20200804_0527'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='requestmeetup', 17 | name='meetup_location', 18 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cities_light.City', verbose_name='Meetup Location'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /systers_portal/meetup/migrations/0005_meetup_start_url.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.9 on 2020-08-05 06:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('meetup', '0004_auto_20200804_0538'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='meetup', 15 | name='start_url', 16 | field=models.URLField(blank=True, null=True, verbose_name='link for the Host to start the Meeting'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /systers_portal/meetup/migrations/0006_auto_20200805_0639.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.9 on 2020-08-05 06:39 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('meetup', '0005_meetup_start_url'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='meetup', 15 | name='start_url', 16 | field=models.URLField(blank=True, max_length=500, null=True, verbose_name='link for the Host to start the Meeting'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /systers_portal/meetup/migrations/0007_auto_20200805_0641.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.9 on 2020-08-05 06:41 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('meetup', '0006_auto_20200805_0639'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='meetup', 15 | name='start_url', 16 | field=models.URLField(blank=True, max_length=1000, null=True, verbose_name='link for the Host to start the Meeting'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /systers_portal/meetup/migrations/0008_meetup_meeting_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.9 on 2020-08-05 17:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('meetup', '0007_auto_20200805_0641'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='meetup', 15 | name='meeting_id', 16 | field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Meeting ID'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /systers_portal/meetup/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/meetup/migrations/__init__.py -------------------------------------------------------------------------------- /systers_portal/meetup/permissions.py: -------------------------------------------------------------------------------- 1 | from meetup.constants import * 2 | 3 | groups_templates = {"community_member": COMMUNITY_MEMBER, 4 | "community_moderator": COMMUNITY_MODERATOR, 5 | "community_leader": COMMUNITY_LEADER} 6 | 7 | community_member_permissions = [ 8 | "add_meetup_rsvp", 9 | "add_support_request" 10 | ] 11 | 12 | community_moderator_permissions = community_member_permissions + [ 13 | "add_meetups", 14 | "change_meetups", 15 | "delete_meetups", 16 | "approve_meetup_request", 17 | "reject_meetup_request", 18 | "view_meetup_request", 19 | "approve_support_request", 20 | "reject_support_request", 21 | "add_resource" 22 | ] 23 | 24 | community_leader_permissions = community_moderator_permissions 25 | 26 | group_permissions = {"community_member": community_member_permissions, 27 | "community_moderator": community_moderator_permissions, 28 | "community_leader": community_leader_permissions} 29 | -------------------------------------------------------------------------------- /systers_portal/meetup/tests/selenium/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pytest 3 | from selenium import webdriver 4 | 5 | browsers = { 6 | 'firefox': webdriver.Firefox, 7 | 'chrome': webdriver.Chrome, 8 | } 9 | 10 | 11 | @pytest.fixture(scope="class") 12 | def browser(request): 13 | if 'DISPLAY' not in os.environ: 14 | pytest.skip('Test requires display server (export DISPLAY)') 15 | 16 | b = browsers[request.config.getoption("--browser-config")]() 17 | 18 | request.addfinalizer(lambda *args: b.quit()) 19 | 20 | # inject class variables 21 | request.cls.browser = b 22 | 23 | return b 24 | 25 | 26 | def pytest_addoption(parser): 27 | parser.addoption( 28 | "--browser-config", 29 | action="store", 30 | default="chrome", 31 | help="firefox, chrome are allowed parameters") 32 | -------------------------------------------------------------------------------- /systers_portal/meetup/tests/selenium/test_meetup_location_admin_page.py: -------------------------------------------------------------------------------- 1 | from common.tests.selenium.base import SeleniumTestCase 2 | 3 | 4 | class TestMeetupLocationAdminPage(SeleniumTestCase): 5 | """ 6 | Extended automated visual tests for admin actions in meetup app, 7 | test_can_create_meetup removed as its covered in test_admin_actions under common tests. 8 | """ 9 | 10 | def test_can_delete_meetup(self): 11 | self.browser.get(self.live_server_url) 12 | self.browser.add_cookie(self.create_session_cookie()) 13 | self.browser.refresh() 14 | self.browser.get('{}{}'.format(self.live_server_url, '/meetup/foo/about')) 15 | self.browser.find_element_by_xpath("//a[contains(text(),'Delete Meetup Location')]").click() 16 | self.browser.find_element_by_xpath('//input[@value="Confirm"]').click() 17 | self.assertTrue('Meetup Locations' in self.browser.title) 18 | -------------------------------------------------------------------------------- /systers_portal/meetup/tests/selenium/test_meetup_location_page.py: -------------------------------------------------------------------------------- 1 | from common.tests.selenium.base import SeleniumTestCase 2 | 3 | 4 | class TestMeetupLocationPage(SeleniumTestCase): 5 | """ 6 | Automated visual tests for meetup location page. 7 | """ 8 | 9 | def test_can_choose_location(self): 10 | self.browser.get('{}{}'.format(self.live_server_url, '/meetup/locations/')) 11 | self.browser.find_element_by_xpath("//a[contains(text(),'Foo Systers')]").click() 12 | self.assertTrue('Foo Systers' in self.browser.title) 13 | 14 | def test_can_get_members(self): 15 | self.browser.get('{}{}'.format(self.live_server_url, '/meetup/foo/about')) 16 | self.browser.find_element_by_xpath("//a[contains(text(),'Members')]").click() 17 | text = self.browser.find_element_by_xpath("//h2[contains(text(),'Moderators')]").text 18 | self.assertTrue(text in self.browser.page_source) 19 | -------------------------------------------------------------------------------- /systers_portal/meetup/tests/selenium/test_meetup_locations_page.py: -------------------------------------------------------------------------------- 1 | from common.tests.selenium.base import SeleniumTestCase 2 | 3 | 4 | class TestMeetupLocationsPage(SeleniumTestCase): 5 | """ 6 | Automated visual tests for meetup locations page. 7 | """ 8 | 9 | def test_can_goto_meetuplocations_page(self): 10 | self.browser.get('{}{}'.format(self.live_server_url, '/meetup/locations/')) 11 | self.assertTrue('Meetup Locations' in self.browser.title) 12 | 13 | def test_can_choose_meetup_locations(self): 14 | self.browser.get(self.live_server_url) 15 | dropdown = self.browser.find_element_by_id('meetup-dropdown') 16 | dropdown.click() 17 | menu = dropdown.find_element_by_class_name('dropdown-menu') 18 | for menu_item in menu.find_elements_by_tag_name('li'): 19 | if menu_item.text == 'MEETUP LOCATIONS': 20 | menu_item.click() 21 | break 22 | self.assertTrue('Meetup Locations' in self.browser.title) 23 | 24 | def test_can_choose_location(self): 25 | self.browser.get('{}{}'.format(self.live_server_url, '/meetup/locations/')) 26 | self.browser.find_element_by_xpath("//a[contains(text(),'Foo Systers')]").click() 27 | self.assertTrue('Foo Systers' in self.browser.title) 28 | -------------------------------------------------------------------------------- /systers_portal/meetup/tests/selenium/test_meetup_page.py: -------------------------------------------------------------------------------- 1 | from common.tests.selenium.base import SeleniumTestCase 2 | 3 | 4 | class TestMeetupPage(SeleniumTestCase): 5 | """ 6 | Automated visual tests for meetup entry page. 7 | """ 8 | 9 | def test_can_goto_meetup_page(self): 10 | self.browser.get('{}{}'.format(self.live_server_url, '/meetup/foo/baz/')) 11 | title = self.browser.find_element_by_id('meetup-title') 12 | self.assertTrue(title.text == 'Test Meetup') 13 | -------------------------------------------------------------------------------- /systers_portal/membership/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'ana' 2 | -------------------------------------------------------------------------------- /systers_portal/membership/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from membership.models import JoinRequest 4 | 5 | 6 | admin.site.register(JoinRequest) 7 | -------------------------------------------------------------------------------- /systers_portal/membership/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from common.helpers import SubmitCancelFormHelper 4 | 5 | 6 | class TransferOwnershipForm(forms.Form): 7 | """Form with a single field that allow a single choice out of a list of 8 | members of a community. Used to select the new admin of a community.""" 9 | def __init__(self, *args, **kwargs): 10 | self.community = kwargs.pop('community') 11 | super(TransferOwnershipForm, self).__init__(*args, **kwargs) 12 | members = self.community.members.all() 13 | members = members.exclude(pk=self.community.admin.pk) 14 | choices = [(member.id, str(member)) for member in members] 15 | self.fields['new_admin'] = forms.ChoiceField( 16 | choices=choices, label="New community admin") 17 | 18 | self.helper = SubmitCancelFormHelper( 19 | self, cancel_href="{% url 'user' user.username %}") 20 | -------------------------------------------------------------------------------- /systers_portal/membership/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.8 on 2020-07-24 20:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='JoinRequest', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('date_created', models.DateTimeField(auto_now_add=True)), 19 | ('is_approved', models.BooleanField(default=False)), 20 | ], 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /systers_portal/membership/migrations/0002_auto_20200724_2045.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.8 on 2020-07-24 20:45 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('users', '0001_initial'), 13 | ('community', '0001_initial'), 14 | ('membership', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='joinrequest', 20 | name='approved_by', 21 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='approved_by', to='users.SystersUser'), 22 | ), 23 | migrations.AddField( 24 | model_name='joinrequest', 25 | name='community', 26 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='community.Community'), 27 | ), 28 | migrations.AddField( 29 | model_name='joinrequest', 30 | name='user', 31 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_by', to='users.SystersUser'), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /systers_portal/membership/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/membership/migrations/__init__.py -------------------------------------------------------------------------------- /systers_portal/membership/tests/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'ana' 2 | -------------------------------------------------------------------------------- /systers_portal/membership/tests/test_forms.py: -------------------------------------------------------------------------------- 1 | from cities_light.models import Country, City 2 | from django import forms 3 | from django.contrib.auth.models import User 4 | from django.test import TestCase 5 | 6 | from community.models import Community 7 | from membership.forms import TransferOwnershipForm 8 | from users.models import SystersUser 9 | 10 | 11 | class TransferOwnershipFormTestCase(TestCase): 12 | def setUp(self): 13 | self.user = User.objects.create_user(username='foo', password='foobar') 14 | self.systers_user = SystersUser.objects.get(user=self.user) 15 | country = Country.objects.create(name='Bar', continent='AS') 16 | location = City.objects.create(name='Foo', display_name='Foo', 17 | country=country) 18 | self.community = Community.objects.create(name="Foo", slug="foo", 19 | order=1, location=location, 20 | admin=self.systers_user) 21 | 22 | def test_transfer_ownership_form(self): 23 | """Test transferring ownership form""" 24 | form = TransferOwnershipForm(community=self.community) 25 | self.assertIsInstance(form.fields['new_admin'], forms.ChoiceField) 26 | self.assertSequenceEqual(form.fields['new_admin'].choices, []) 27 | 28 | bar_user = User.objects.create_user(username="bar", password="foobar") 29 | bar_systers_user = SystersUser.objects.get(user=bar_user) 30 | self.community.add_member(bar_systers_user) 31 | 32 | User.objects.create_user(username="new", password="foobar") 33 | 34 | form = TransferOwnershipForm(community=self.community) 35 | self.assertSequenceEqual(form.fields['new_admin'].choices, 36 | [(bar_user.id, "bar")]) 37 | -------------------------------------------------------------------------------- /systers_portal/membership/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from membership.views import (CommunityJoinRequestListView, 4 | ApproveCommunityJoinRequestView, 5 | RejectCommunityJoinRequestView, 6 | RequestJoinCommunityView, 7 | CancelCommunityJoinRequestView, 8 | LeaveCommunityView, TransferOwnershipView, 9 | RemoveCommunityMemberView) 10 | 11 | urlpatterns = [ 12 | url(r'^(?P[\w-]+)/join_requests/$', 13 | CommunityJoinRequestListView.as_view(), 14 | name="view_community_join_request_list"), 15 | url(r'^(?P[\w-]+)/join_requests/approve/(?P\d+)$', 16 | ApproveCommunityJoinRequestView.as_view(), 17 | name="approve_community_join_request"), 18 | url(r'^(?P[\w-]+)/join_requests/reject/(?P\d+)$', 19 | RejectCommunityJoinRequestView.as_view(), 20 | name="reject_community_join_request"), 21 | url(r'^(?P[\w-]+)/join/$', RequestJoinCommunityView.as_view(), 22 | name="request_join_community"), 23 | url(r'^(?P[\w-]+)/cancel/$', 24 | CancelCommunityJoinRequestView.as_view(), 25 | name="cancel_community_join_request"), 26 | url(r'^(?P[\w-]+)/leave/$', LeaveCommunityView.as_view(), 27 | name="leave_community"), 28 | url(r'^(?P[\w-]+)/transfer_ownership/$', 29 | TransferOwnershipView.as_view(), name="transfer_ownership"), 30 | url(r'^(?P[\w-]+)/remove/(?P[\w.@+-]+)/$', 31 | RemoveCommunityMemberView.as_view(), name="remove_member"), 32 | ] 33 | -------------------------------------------------------------------------------- /systers_portal/static/css/date-time-picker/classic.css: -------------------------------------------------------------------------------- 1 | .picker,.picker__holder{width:100%;position:absolute}.picker{font-size:16px;text-align:left;line-height:1.2;color:#000;z-index:10000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.picker__input{cursor:default}.picker__input.picker__input--active{border-color:#0089ec}.picker__holder{overflow-y:auto;-webkit-overflow-scrolling:touch;background:#fff;border:1px solid #aaa;border-top-width:0;border-bottom-width:0;border-radius:0 0 5px 5px;box-sizing:border-box;min-width:176px;max-width:466px;max-height:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:alpha(opacity=0);-moz-opacity:0;opacity:0;-webkit-transform:translateY(-1em)perspective(600px)rotateX(10deg);transform:translateY(-1em)perspective(600px)rotateX(10deg);transition:-webkit-transform .15s ease-out,opacity .15s ease-out,max-height 0s .15s,border-width 0s .15s;transition:transform .15s ease-out,opacity .15s ease-out,max-height 0s .15s,border-width 0s .15s}/*! 2 | * Classic picker styling for pickadate.js 3 | * Demo: http://amsul.github.io/pickadate.js 4 | */.picker__frame{padding:1px}.picker__wrap{margin:-1px}.picker--opened .picker__holder{max-height:25em;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:alpha(opacity=100);-moz-opacity:1;opacity:1;border-top-width:1px;border-bottom-width:1px;-webkit-transform:translateY(0)perspective(600px)rotateX(0);transform:translateY(0)perspective(600px)rotateX(0);transition:-webkit-transform .15s ease-out,opacity .15s ease-out,max-height 0s,border-width 0s;transition:transform .15s ease-out,opacity .15s ease-out,max-height 0s,border-width 0s;box-shadow:0 6px 18px 1px rgba(0,0,0,.12)} -------------------------------------------------------------------------------- /systers_portal/static/css/date-time-picker/classic.time.css: -------------------------------------------------------------------------------- 1 | .picker--focused .picker__list-item--highlighted,.picker__list-item--highlighted:hover,.picker__list-item:hover{background:#b1dcfb;cursor:pointer;color:#000}.picker__list{list-style:none;padding:.75em 0 4.2em;margin:0}.picker__list-item{border-bottom:1px solid #ddd;border-top:1px solid #ddd;margin-bottom:-1px;position:relative;background:#fff;padding:.75em 1.25em}@media (min-height:46.75em){.picker__list-item{padding:.5em 1em}}.picker__list-item--highlighted,.picker__list-item:hover{border-color:#0089ec;z-index:10}.picker--focused .picker__list-item--selected,.picker__list-item--selected,.picker__list-item--selected:hover{background:#0089ec;color:#fff;z-index:10}.picker--focused .picker__list-item--disabled,.picker__list-item--disabled,.picker__list-item--disabled:hover{background:#f5f5f5;color:#ddd;cursor:default;border-color:#ddd;z-index:auto}.picker--time .picker__button--clear{display:block;width:80%;margin:1em auto 0;padding:1em 1.25em;background:0 0;border:0;font-weight:500;font-size:.67em;text-align:center;text-transform:uppercase;color:#666}.picker--time .picker__button--clear:focus,.picker--time .picker__button--clear:hover{background:#e20;border-color:#e20;cursor:pointer;color:#fff;outline:0}.picker--time .picker__button--clear:before{top:-.25em;color:#666;font-size:1.25em;font-weight:700}.picker--time .picker__button--clear:focus:before,.picker--time .picker__button--clear:hover:before{color:#fff;border-color:#fff}.picker--time{min-width:256px;max-width:320px}.picker--time .picker__holder{background:#f2f2f2}@media (min-height:40.125em){.picker--time .picker__holder{font-size:.875em}}.picker--time .picker__box{padding:0;position:relative} -------------------------------------------------------------------------------- /systers_portal/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /systers_portal/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /systers_portal/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /systers_portal/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /systers_portal/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /systers_portal/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /systers_portal/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /systers_portal/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /systers_portal/static/img/about_systers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/about_systers.jpg -------------------------------------------------------------------------------- /systers_portal/static/img/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/default.png -------------------------------------------------------------------------------- /systers_portal/static/img/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/facebook.png -------------------------------------------------------------------------------- /systers_portal/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/favicon.ico -------------------------------------------------------------------------------- /systers_portal/static/img/google.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/google.jpg -------------------------------------------------------------------------------- /systers_portal/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/logo.png -------------------------------------------------------------------------------- /systers_portal/static/img/systers_communities.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/systers_communities.jpg -------------------------------------------------------------------------------- /systers_portal/static/img/systers_craw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/systers_craw.jpg -------------------------------------------------------------------------------- /systers_portal/static/img/systers_initiatives.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/systers_initiatives.jpg -------------------------------------------------------------------------------- /systers_portal/static/img/systers_technical_interests.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/systers_technical_interests.jpg -------------------------------------------------------------------------------- /systers_portal/static/img/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/static/img/twitter.png -------------------------------------------------------------------------------- /systers_portal/static/js/choose_profile_pic.js: -------------------------------------------------------------------------------- 1 | // Altered content for the form loaded by crispy forms 2 | var htmlInnerContent="
\n" + 4 | "\n" + 6 | "
"; 9 | 10 | // Changing the content of the required div containing photo upload field rendered by crispy forms 11 | $("#div_id_profile_picture").html(htmlInnerContent); 12 | 13 | // When custom button is clicked, perform a click on file upload field 14 | $("#upload_pic").click(function() { 15 | $("#id_profile_picture").click(); 16 | }); 17 | 18 | // When file has been selected, this call back is triggered 19 | document.getElementById("id_profile_picture").onchange = function () { 20 | var str = this.value; 21 | // Split the path of file uploaded 22 | var pathTokens = str.split("\\"); 23 | // Display name of file beside the custom button 24 | $("#file_name_display").html(" " + pathTokens[pathTokens.length - 1]); 25 | }; 26 | -------------------------------------------------------------------------------- /systers_portal/static/js/community-search.js: -------------------------------------------------------------------------------- 1 | const input = $("#form1") 2 | const result_div = $('#cards') 3 | const endpoint = '/community/search' 4 | const delay_by_in_ms =300 5 | let scheduled_function = false 6 | 7 | let ajax_call = function (endpoint, request_parameters) { 8 | $.getJSON(endpoint, request_parameters) 9 | .done(response => { 10 | result_div.fadeTo('fast', 0).promise().then(() => { 11 | result_div.html(response['html']) 12 | result_div.fadeTo('fast', 1) 13 | 14 | }) 15 | }) 16 | } 17 | input.on('keyup', function () { 18 | 19 | const request_parameters = { 20 | query: $(this).val() 21 | } 22 | if (scheduled_function) { 23 | clearTimeout(scheduled_function) 24 | } 25 | scheduled_function = setTimeout(ajax_call, delay_by_in_ms, endpoint, request_parameters) 26 | }) 27 | -------------------------------------------------------------------------------- /systers_portal/static/js/pin.js: -------------------------------------------------------------------------------- 1 | function getCookie(name) { 2 | var cookieValue = null; 3 | if (document.cookie && document.cookie !== "") { 4 | var cookies = document.cookie.split(";"); 5 | cookies.forEach(function(cookie){ 6 | cookie = jQuery.trim(cookie); 7 | // Does this cookie string begin with the name we want? 8 | inner:if (cookie.substring(0, name.length + 1) === (name + "=")) { 9 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 10 | break inner; 11 | } 12 | }); 13 | } 14 | return cookieValue; 15 | } 16 | $("#pin").click(function() { 17 | let csrftoken = getCookie("csrftoken") 18 | let Data = { 19 | "csrfmiddlewaretoken": csrftoken, 20 | }; 21 | $.ajax({ 22 | type: "POST", 23 | url: "pin/", 24 | data: Data, 25 | success(a){ 26 | $("#pin").hide(); 27 | $("#remove").show(); 28 | }, 29 | dataType:"json", 30 | }); 31 | }); 32 | 33 | $("#remove").click(function() { 34 | let csrftoken = getCookie("csrftoken") 35 | let Data = { 36 | "csrfmiddlewaretoken": csrftoken, 37 | }; 38 | $.ajax({ 39 | type: "POST", 40 | url: "unpin/", 41 | data: Data, 42 | success(a){ 43 | $("#remove").hide(); 44 | $("#pin").show(); 45 | }, 46 | dataType:"json", 47 | }); 48 | }); 49 | 50 | $(function(){ 51 | $(".unpin").click(function(){ 52 | let id = this.id; 53 | let s = ".blog-entry" + "." + id; 54 | 55 | let csrftoken = getCookie("csrftoken") 56 | let Data = { 57 | "id": id, 58 | "csrfmiddlewaretoken": csrftoken, 59 | }; 60 | $.ajax({ 61 | type: "POST", 62 | url: "unpin/", 63 | data: Data, 64 | success(a){ 65 | $(s).hide(); 66 | }, 67 | dataType:"json", 68 | }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /systers_portal/static/js/select.js: -------------------------------------------------------------------------------- 1 | $(document).ready(()=>{ 2 | const optionlength = $('select > option').length; 3 | if(optionlength>1500){ 4 | $('select').select2({ 5 | minimumInputLength:1 6 | }); 7 | }else{ 8 | $('select').select2(); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /systers_portal/systers_portal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/systers_portal/__init__.py -------------------------------------------------------------------------------- /systers_portal/systers_portal/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/systers_portal/settings/__init__.py -------------------------------------------------------------------------------- /systers_portal/systers_portal/settings/dev.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | from decouple import config 3 | 4 | SCHEDULER_AUTOSTART = True 5 | DEBUG = config('DEBUG', default=False, cast=bool) 6 | TEMPLATES[0]['OPTIONS']['debug'] = DEBUG 7 | 8 | DATABASES = { 9 | 'default': { 10 | 'ENGINE': 'django.db.backends.postgresql', 11 | 'NAME': config('DB_NAME', default='systersdb'), 12 | 'USER': config('DB_USER'), 13 | 'PASSWORD': config('DB_PASSWORD'), 14 | 'HOST': config('DB_HOST', default='localhost'), 15 | 'PORT': config('DB_PORT', default=5432, cast=int), 16 | } 17 | } 18 | 19 | INTERNAL_IPS = ('127.0.0.1',) 20 | 21 | # Instead of sending out real email, during development the emails will be sent 22 | # to stdout, where from they can be inspected. 23 | EMAIL_HOST = config('EMAIL_HOST', default='localhost') 24 | EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int) 25 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 26 | FROM_EMAIL = os.environ.get('FROM_EMAIL') 27 | -------------------------------------------------------------------------------- /systers_portal/systers_portal/settings/docker.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | DEBUG = True 4 | TEMPLATES[0]['OPTIONS']['debug'] = DEBUG 5 | 6 | DATABASES = { 7 | 'default': { 8 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 9 | 'NAME': 'postgres', 10 | 'USER': 'postgres', 11 | 'PASSWORD': 'mysecretpassword', 12 | 'HOST': 'db', 13 | 'PORT': '5432', 14 | } 15 | } 16 | 17 | INTERNAL_IPS = ('127.0.0.1',) 18 | 19 | # Instead of sending out real email, during development the emails will be sent 20 | # to stdout, where from they can be inspected. 21 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 22 | -------------------------------------------------------------------------------- /systers_portal/systers_portal/settings/production.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | DEBUG = False 4 | TEMPLATES[0]['OPTIONS']['debug'] = DEBUG 5 | 6 | DATABASES = { 7 | 'default': { 8 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 9 | 'NAME': '', 10 | 'USER': '', 11 | 'PASSWORD': '', 12 | 'HOST': 'localhost', 13 | 'PORT': '5432', 14 | } 15 | } 16 | INTERNAL_IPS = ('127.0.0.1',) 17 | -------------------------------------------------------------------------------- /systers_portal/systers_portal/settings/testing.py: -------------------------------------------------------------------------------- 1 | from .base import * 2 | 3 | SCHEDULER_AUTOSTART = False 4 | DEBUG = True 5 | TEMPLATES[0]['OPTIONS']['debug'] = DEBUG 6 | 7 | INSTALLED_APPS += ( 8 | 'django_nose', 9 | ) 10 | 11 | DATABASES = { 12 | 'default': { 13 | 'ENGINE': 'django.db.backends.sqlite3', 14 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 15 | } 16 | } 17 | 18 | INTERNAL_IPS = ('127.0.0.1',) 19 | 20 | ROOT_URLCONF = 'systers_portal.systers_portal.urls' 21 | 22 | TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' 23 | 24 | NOSE_ARGS = [ 25 | '--nocapture', 26 | '--nologcapture', 27 | # '--with-doctest', 28 | # '--doctest-options=+ELLIPSIS', 29 | ] 30 | 31 | PASSWORD_HASHERS = ( 32 | 'django.contrib.auth.hashers.MD5PasswordHasher', 33 | ) 34 | -------------------------------------------------------------------------------- /systers_portal/systers_portal/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for systers_portal project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "systers_portal.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /systers_portal/templates/403.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} - Access Denied{% endblock %} 4 | 5 | {% block content %} 6 | 7 |
8 |

403

9 |

Access Denied

10 |

Unfortunately, you don't have permission to access this page.

11 |

12 | Back to previous page 13 |

14 |
15 | 16 |
17 | {% endblock %} 18 | {% block scripts %} 19 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /systers_portal/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} - Page Not Found{% endblock %} 4 | 5 | {% block content %} 6 | 7 |
8 |

404

9 |

Page Not Found

10 |

Unfortunately, this page does not exist. Please check your URL or return back to the previous page.

11 |

12 | Back to previous page 13 |

14 |
15 | 16 | 17 |
18 | {% endblock %} 19 | {% block scripts %} 20 | 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /systers_portal/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} - Server Error{% endblock %} 4 | 5 | {% block content %} 6 | 7 |
8 |

500

9 |

Server Error/Internal Server Error

10 |

An unexpected error seems to have occured. Try refreshing the page or return to the previous page.

11 |

12 | Back to previous page 13 |

14 |
15 | 16 |
17 | {% endblock %} 18 | {% block scripts %} 19 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /systers_portal/templates/account/account_inactive.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} - Account Inactive{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

Account Inactive

7 |
8 |
9 |

This account is inactive.

10 |
11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /systers_portal/templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load account %} 4 | 5 | {% block title %} - Confirm E-mail Address{% endblock %} 6 | 7 | {% block content %} 8 |
9 |

Confirm E-mail Address

10 |
11 | {% if confirmation %} 12 | {% user_display confirmation.email_address.user as user_display %} 13 |

Please confirm that {{ confirmation.email_address.email }} is an e-mail address for user {{ user_display }}.

14 |
15 | {% csrf_token %} 16 | 17 |
18 | {% else %} 19 | {% url 'account_email' as email_url %} 20 |

This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.

21 | {% endif %} 22 |
23 | 24 |
25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /systers_portal/templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load account %} 4 | 5 | {% block title %} - Sign In{% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 | {% load socialaccount %} 10 | {% load static %} 11 |
12 | 34 | 37 |
38 | 39 |
40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /systers_portal/templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} - Sign Out{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

Sign Out

7 |
8 |

Are you sure you want to sign out?

9 | {% csrf_token %} 10 | {% if redirect_field_value %} 11 | 12 | {% endif %} 13 |
14 | 15 |
16 |
17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /systers_portal/templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | {% block title %} - Change Password{% endblock %} 4 | {% block content %} 5 |
6 |
7 |

Change Password

8 |
9 | {% csrf_token %} 10 | {{ form|crispy }} 11 |
12 | 15 |

Please contact us if you have any trouble in changing your password.

16 |
17 |
18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /systers_portal/templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load account %} 3 | {% load crispy_forms_tags %} 4 | {% block title %} - Password Reset{% endblock %} 5 | 6 | {% block content %} 7 |
8 |
9 |

Password Reset

10 |
11 | {% if user.is_authenticated %} 12 | {% include "account/snippets/already_logged_in.html" %} 13 | {% endif %} 14 |

Forgotten your password?

15 |

Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it.

16 | 17 | {% csrf_token %} 18 | {{ form|crispy }} 19 |
20 | 23 |

Please contact us if you have any trouble resetting your password.

24 |
25 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /systers_portal/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load account %} 4 | 5 | {% block title %} - Sign Up{% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 | {% load socialaccount %} 10 | {% load static %} 11 |
12 | 32 |
33 | 34 |
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /systers_portal/templates/account/verification_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} - Verify Your E-mail Address{% endblock %} 3 | 4 | {% block content %} 5 |
6 |

Verify Your E-mail Address

7 |
8 |

We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.

9 |
10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /systers_portal/templates/blog/post_list.html: -------------------------------------------------------------------------------- 1 | {% extends "community/base.html" %} 2 | 3 | {% block title %} 4 | - {{ community }} {% if post_type == "news" %}News{% else %}Resources{% endif %} 5 | {% endblock %} 6 | 7 | {% block community_page_content %} 8 |
9 | {% for post in object_list %} 10 |
11 |

{{ post.title }}

12 | 13 |

{{ post.date_modified }} | {{ post.author }} 15 | {% if post.resource_type %} 16 | | {{ post.resource_type }} 17 | {% endif %} 18 |

19 | 20 |
{{ post.content|safe|truncatewords:50 }}
21 | {% if post.tags %} 22 |
    23 | {% for tag in post.tags.all %} 24 |
  • {{ tag }}
  • 25 | {% endfor %} 26 |
27 | {% endif %} 28 |
29 |
30 | {% endfor %} 31 | {% include "blog/snippets/pagination.html" %} 32 |
33 | {% endblock %} 34 | 35 | {% block extra_sidebar %} 36 | {% if post_type == "news" %} 37 | {% include 'blog/snippets/news_sidebar.html' %} 38 | {% else %} 39 | {% include 'blog/snippets/resources_sidebar.html' %} 40 | {% include 'blog/snippets/resource_types.html' %} 41 | {% endif %} 42 | {% include 'blog/snippets/tags_sidebar.html' %} 43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /systers_portal/templates/blog/snippets/news_sidebar.html: -------------------------------------------------------------------------------- 1 | {% load guardian_tags %} 2 | 3 | {% if user.is_authenticated and user.is_active %} 4 | {% get_obj_perms user for community as "community_perms" %} 5 | {% if "add_community_news" in community_perms %} 6 | 18 | {% endif %} 19 | {% endif %} 20 | -------------------------------------------------------------------------------- /systers_portal/templates/blog/snippets/pagination.html: -------------------------------------------------------------------------------- 1 | {% if is_paginated %} 2 | 39 | {% endif %} 40 | -------------------------------------------------------------------------------- /systers_portal/templates/blog/snippets/resource_types.html: -------------------------------------------------------------------------------- 1 | {% if resource_types %} 2 | 12 | {% endif %} 13 | -------------------------------------------------------------------------------- /systers_portal/templates/blog/snippets/resources_sidebar.html: -------------------------------------------------------------------------------- 1 | {% load guardian_tags %} 2 | 3 | {% if user.is_authenticated and user.is_active %} 4 | {% get_obj_perms user for community as "community_perms" %} 5 | {% if "add_community_resource" in community_perms %} 6 | 18 | {% endif %} 19 | {% endif %} 20 | -------------------------------------------------------------------------------- /systers_portal/templates/blog/snippets/tags_sidebar.html: -------------------------------------------------------------------------------- 1 | {% if user.is_authenticated and user.is_active %} 2 | {% if perms.blog.add_tag %} 3 | 9 | {% endif %} 10 | {% endif %} 11 | -------------------------------------------------------------------------------- /systers_portal/templates/blog/tag_type.html: -------------------------------------------------------------------------------- 1 | {% extends "community/base.html" %} 2 | 3 | {% block title %} 4 | - Add a new {{ tag_type }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

Add a new {{ tag_type }}

13 |
14 |
15 |
16 |
17 | {% crispy form %} 18 |
19 |
20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /systers_portal/templates/bootstrap3/layout/checkboxselectmultiple.html: -------------------------------------------------------------------------------- 1 | {% load crispy_forms_filters %} 2 | {% load l10n %} 3 | 4 |
6 | {% include 'bootstrap3/layout/field_errors_block.html' %} 7 | {% for choice in field.field.choices %} 8 |
9 | 16 |
17 | {% endfor %} 18 | 19 | {% include 'bootstrap3/layout/help_text.html' %} 20 |
21 | -------------------------------------------------------------------------------- /systers_portal/templates/common/about_us.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 |
6 |
7 |

About Systers

8 |
9 | Systers Communities 11 |
12 |
13 |

14 | It’s important to know that you are not alone. Systers is a forum for all women involved in the technical aspects 15 | of computing. 16 | The list has over 4,000 members from at least 54 countries around the world. We welcome the participation of women 17 | technologists of 18 | all ages and at any stage of their studies or careers. Systers is the world’s largest email community of women in 19 | technical roles in 20 | computing. It was founded by Anita Borg in 1987 together with 12 other women as a small electronic mailing list 21 | for women in 22 | “systems”. 23 |

24 |

25 | Systers Portal is a unified platform for Systers and its sub-groups to share information and get the latest news. 26 | Special Interest 27 | communities are also offered where Systers can identify themselves culturally to share and offer support in 28 | challenges within 29 | their communities. 30 |

31 |
32 |
33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /systers_portal/templates/common/add_post.html: -------------------------------------------------------------------------------- 1 | {% extends "community/base.html" %} 2 | 3 | {% block title %} 4 | - Add {{ post_type }} to {{ community }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

Add {{ post_type }} to {{ community }}

13 |
14 |
15 |
16 |
17 | {{ form.media }} 18 | {% crispy form %} 19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /systers_portal/templates/common/contact.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% block content %} 4 | 5 |
6 |

Contact Systers

7 |
8 |

9 | Systers is curated by the current Systers-keeper, Rose Robinson. 10 |

11 |

12 | You can also connect with Systers on our social networks. 13 |

14 |
15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /systers_portal/templates/common/edit_post.html: -------------------------------------------------------------------------------- 1 | {% extends "community/base.html" %} 2 | 3 | {% block title %} 4 | - Edit "{{ object.title }}" 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

Edit "{{ object.title }}"

13 |
14 |
15 |
16 |
17 | {{ form.media }} 18 | {% crispy form %} 19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /systers_portal/templates/common/new_community_proposal.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | {% block content %} 4 | 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /systers_portal/templates/common/post_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - {{ community }} - Delete {{ object.title }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 | 10 | {% if post_type == "news" %} 11 | {% url 'delete_community_news' community.slug object.slug as delete_url %} 12 | {% elif post_type == "resource" %} 13 | {% url 'delete_community_resource' community.slug object.slug as delete_url %} 14 | {% else %} 15 | {% url 'delete_community_page' community.slug object.slug as delete_url %} 16 | {% endif %} 17 | 18 |
19 |
20 |
21 |

Confirm to delete "{{ object.title }}"

22 |
23 |
24 |
25 |
26 | {% csrf_token %} 27 |

Are you sure you want to delete "{{ object.title }}"?

28 | 29 |
30 |
31 |
32 | {% endblock %} -------------------------------------------------------------------------------- /systers_portal/templates/community/add_community.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - Add Community 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

Add community

13 |
14 |
15 |
16 |
17 | {% crispy form %} 18 |
19 |
20 |
21 | {% endblock %} 22 | 23 | {% block community_footer %} 24 | {% include 'community/snippets/footer.html' %} 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /systers_portal/templates/community/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load verbose_name %} 3 | 4 | {% block title %} 5 | - {{ community }} 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 |

{{ community }}

13 |
14 |
15 |
16 | 28 | 29 | {% block community_page_content %} 30 | {% endblock %} 31 | 32 |
33 |
34 | {% include 'community/snippets/community_sidebar.html' %} 35 | {% include "community/snippets/page_sidebar.html" %} 36 | {% block extra_sidebar %}{% endblock %} 37 | {% include 'community/snippets/join_button.html' %} 38 |
39 |
40 | {% endblock %} 41 | 42 | {% block community_footer %} 43 | {% include 'community/snippets/footer.html' %} 44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /systers_portal/templates/community/community_search.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |

Search for Communities


4 |
5 | 6 |
7 |
8 |
9 | {%include 'community/snippets/community-map.html'%} 10 |
11 | {% include 'community/snippets/search-snippet.html' %} 12 |
13 | {%endblock content%} 14 | -------------------------------------------------------------------------------- /systers_portal/templates/community/confirm_reject_request_community.html: -------------------------------------------------------------------------------- 1 | {{% extends "base.html" %} 2 | 3 | {% block title %} 4 | Delete {{ object.name }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block content %} 10 |
11 |
12 |

Confirm to Delete {{ object.name }}

13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {% csrf_token %} 23 |

Are you sure you want to delete "{{ object.name }}"?

24 | 25 |
26 |
27 |
28 |
29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /systers_portal/templates/community/edit_community_request.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Edit {{ community_request }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

Edit {{ community_request }}

13 |
14 |
15 |
16 |
17 | {% crispy form %} 18 |
19 |
20 |
21 | {% endblock %} 22 | 23 | 24 | -------------------------------------------------------------------------------- /systers_portal/templates/community/edit_profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - Edit {{ community }} profile 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

Edit {{ community }} profile

13 |
14 |
15 |
16 |
17 | {% crispy form %} 18 |
19 |
20 |
21 | {% endblock %} 22 | 23 | {% block community_footer %} 24 | {% include 'community/snippets/footer.html' %} 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /systers_portal/templates/community/new_community_requests.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | 4 | {% block content %} 5 |
6 |

Unapproved Community Requests

7 | {% for community_request in object_list %} 8 |
9 |

10 | {{ community_request.name}} 11 |

12 |

13 | Requested by 14 | {{ requestor }} 15 |

16 | 17 |
18 | {% endfor %} 19 | {% include "blog/snippets/pagination.html" %} 20 |
21 | {% endblock %} 22 | -------------------------------------------------------------------------------- /systers_portal/templates/community/page.html: -------------------------------------------------------------------------------- 1 | {% extends "community/base.html" %} 2 | 3 | {% block title %} 4 | - {{ community }} - {{ page.title }} 5 | {% endblock %} 6 | 7 | {% block community_page_content %} 8 |
9 |
10 |
{{ page.content|safe }}
11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /systers_portal/templates/community/permissions.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - Manage {{ systersuser }} permission groups for {{ community }} community 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

Manage {{ systersuser }} permission groups for {{ community }} community

13 |
14 |
15 |
16 |
17 | {% crispy form %} 18 |
19 |
20 |
21 | {% endblock %} 22 | 23 | {% block community_footer %} 24 | {% include 'community/snippets/footer.html' %} 25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /systers_portal/templates/community/request_community.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - Request a new Community 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

Request a new community

13 |
14 |
15 |
16 |
17 | {{ form.media }} 18 | {% crispy form %} 19 |
20 |
21 | {% if community_requests %} 22 |
23 |

Your Community requests

24 |
    25 | {% for community_request in community_requests %} 26 |
  1. 27 | {{ community_request }} 28 |
  2. 29 | {% endfor %} 30 |
31 |
32 | {% endif %} 33 |
34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /systers_portal/templates/community/snippets/community-marker.html: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /systers_portal/templates/community/snippets/community_sidebar.html: -------------------------------------------------------------------------------- 1 | {% load guardian_tags %} 2 | 3 | {% if user.is_authenticated and user.is_active %} 4 | {% get_obj_perms user for community as "community_perms" %} 5 | 21 | {% endif %} 22 | -------------------------------------------------------------------------------- /systers_portal/templates/community/snippets/footer.html: -------------------------------------------------------------------------------- 1 | {% if community != 'Systers' %} 2 | © {{ community }}, an Anita Borg Systers Community 3 | {% endif %} 4 | -------------------------------------------------------------------------------- /systers_portal/templates/community/snippets/join_button.html: -------------------------------------------------------------------------------- 1 |
2 | {% if user.is_authenticated and user.is_active %} 3 | {# user is the community admin #} 4 | {% if user == community.admin.user %} 5 | Transfer ownership 6 | {% else %} 7 | {# user is a community member #} 8 | {% if is_member %} 9 | Leave Community 10 | {% else %} 11 | {# user requested to join the community #} 12 | {% if join_request %} 13 | {# user requested to join the community and the request is not yet approved #} 14 | {% if not join_request.is_approved %} 15 | Cancel request 17 | {# user was a community member #} 18 | {% elif not is_member and join_request.is_approved %} 19 | Join Community 21 | {% endif %} 22 | {% else %} 23 | {# user is not a member of the community #} 24 | Join Community 26 | {% endif %} 27 | {% endif %} 28 | {% endif %} 29 | {% endif %} 30 |
31 | -------------------------------------------------------------------------------- /systers_portal/templates/community/snippets/page_sidebar.html: -------------------------------------------------------------------------------- 1 | {% load guardian_tags %} 2 | 3 | {% if user.is_authenticated and user.is_active %} 4 | {% get_obj_perms user for community as "community_perms" %} 5 | {% if "add_community_page" in community_perms %} 6 | 20 | {% endif %} 21 | {% endif %} 22 | -------------------------------------------------------------------------------- /systers_portal/templates/community/snippets/search-snippet.html: -------------------------------------------------------------------------------- 1 | {% include 'community/snippets/community-marker.html'%} 2 | {% if communities %} 3 | 4 |
5 | {% for community in communities %} 6 | 18 | {% endfor %} 19 |
20 | {% else %} 21 |

No communities found

22 | {% endif %} 23 | 24 | -------------------------------------------------------------------------------- /systers_portal/templates/community/view_community_request.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

{{ community_request.name }}

5 |
6 |
7 | 8 | 9 | {% for field,value in community_request_fields %} 10 | 11 | 12 | 13 | 14 | {% endfor %} 15 | 16 |
{{ field }}{{ value }}
17 |
18 | {% if not community_request.is_approved %} 19 | 24 |
25 | {% if user.is_staff %} 26 |
27 |
28 | Approve 30 |
31 |
32 | Reject 34 |
35 |
36 | {% endif %} 37 | {% endif %} 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /systers_portal/templates/community/weekly_digest_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | Hey {{user}}, We have details for {{community}} for you. 11 | We have {{count}} members as of now!. Get the latest news and resources available at the community 12 | by checking out the Systers Portal.
13 | Thank You,
14 | AnitaB.org 15 | 16 | 17 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/about.html: -------------------------------------------------------------------------------- 1 | {% extends "meetup/base.html" %} 2 | 3 | {% block meetup_location_page_content %} 4 |
5 |

About

6 |
7 | {{ meetup_location.description|safe }} 8 |
9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/add_comment.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Add comment to {{ content_object }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |

Add comment to {{ content_object }}

14 |
15 |
16 |
17 |
18 | {{ form.media }} 19 | {% crispy form %} 20 |
21 |
22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/add_meetup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Add meetup 5 | {% endblock %} 6 | 7 | {% block head %} 8 | 9 | 10 | 11 | {% endblock %} 12 | 13 | {% load crispy_forms_tags %} 14 | 15 | {% block content %} 16 |
17 |
18 |
19 |

Add meetup

20 |
21 |
22 |
23 |
24 | {{ form.media }} 25 | {% crispy form %} 26 |
27 |
28 |
29 | {% endblock %} 30 | 31 | {% block scripts %} 32 | 33 | 34 | 35 | 46 | 47 | {% endblock %} 48 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/add_meetup_location.html: -------------------------------------------------------------------------------- 1 | {% extends "meetup/base.html" %} 2 | 3 | {% block title %} 4 | Add Meetup Location 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block content %} 10 |
11 |
12 |

Add Meetup Location

13 |
14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 | {% block meetup_location_page_content %} 22 |
23 |
24 |
25 |
26 | {{ form.media }} 27 | {% crispy form %} 28 |
29 |
30 |
31 | {% endblock %} 32 |
33 |
34 | {% block extra_sidebar %}{% endblock %} 35 |
36 |
37 | {% endblock %} 38 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/add_member.html: -------------------------------------------------------------------------------- 1 | {% extends "meetup/base.html" %} 2 | 3 | {% block title %} 4 | Add member to {{ meetup_location.name }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block meetup_location_page_content %} 10 |
11 |
12 |
13 |

Add member to {{ meetup_location.name }}

14 |
15 |
16 |
17 |
18 | {{ form.media }} 19 | {% crispy form %} 20 |
21 |
22 |
23 | {% endblock %} 24 | 25 | 26 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/add_support_request.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Add support request to {{ meetup.title }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |

Add support request to {{ meetup.title }}

14 |
15 |
16 |
17 |
18 | {{ form.media }} 19 | {% crispy form %} 20 |
21 |
22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | {{meetup}} 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 | {% block meetup_page_content %} 11 | {% endblock %} 12 |
13 |
14 | {% include 'meetup/snippets/meetup_sidebar.html' %} 15 | {% block extra_sidebar %}{% endblock %} 16 | {% include 'meetup/snippets/join_button.html' %} 17 | {% include 'meetup/snippets/add_resource_button.html' %} 18 |
19 |
20 | {% block endpanel %} 21 | {% endblock %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/comment_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - Delete comment 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 | 10 | {% url 'delete_meetup' object.slug as delete_url %} 11 | 12 |
13 |
14 |
15 |

Confirm to delete comment

16 |
17 |
18 |
19 |
20 | {% csrf_token %} 21 |

Are you sure you want to delete this comment?

22 |

"{{ object.body }}{{ object.description }}"

23 | 24 |
25 |
26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/confirm_reject_request_meetup.html: -------------------------------------------------------------------------------- 1 | {{% extends "base.html" %} 2 | 3 | {% block title %} 4 | Delete {{ object.title }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block content %} 10 |
11 |
12 |

Confirm to Delete {{ object.title }}

13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {% csrf_token %} 23 |

Are you sure you want to delete "{{ object.title }}"?

24 | 25 |
26 |
27 |
28 |
29 |
30 | {% endblock %} -------------------------------------------------------------------------------- /systers_portal/templates/meetup/confirm_reject_request_meetup_location.html: -------------------------------------------------------------------------------- 1 | {{% extends "base.html" %} 2 | 3 | {% block title %} 4 | Delete {{ object.name }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block content %} 10 |
11 |
12 |

Confirm to Delete {{ object.name }}

13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {% csrf_token %} 23 |

Are you sure you want to delete "{{ object.name }}"?

24 | 25 |
26 |
27 |
28 |
29 |
30 | {% endblock %} -------------------------------------------------------------------------------- /systers_portal/templates/meetup/edit_comment.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Edit comment 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |

Edit comment

14 |
15 |
16 |
17 |
18 | {{ form.media }} 19 | {% crispy form %} 20 |
21 |
22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/edit_meetup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Edit meetup 5 | {% endblock %} 6 | 7 | {% block head %} 8 | 9 | 10 | 11 | {% endblock %} 12 | 13 | {% load crispy_forms_tags %} 14 | 15 | {% block content %} 16 |
17 |
18 |
19 |

Edit meetup

20 |
21 |
22 |
23 |
24 | {{ form.media }} 25 | {% crispy form %} 26 |
27 |
28 |
29 | {% endblock %} 30 | 31 | {% block scripts %} 32 | 33 | 34 | 35 | 46 | 47 | {% endblock %} 48 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/edit_meetup_location.html: -------------------------------------------------------------------------------- 1 | {% extends "meetup/base.html" %} 2 | 3 | {% block title %} 4 | Edit {{ meetup_location }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block meetup_location_page_content %} 10 |
11 |
12 |
13 |

Edit {{ meetup_location }}

14 |
15 |
16 |
17 |
18 | {{ form.media }} 19 | {% crispy form %} 20 |
21 |
22 |
23 | {% endblock %} 24 | 25 | 26 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/edit_meetup_location_request.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Edit {{ meetup_location_request }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

Edit {{ meetup_location_request }}

13 |
14 |
15 |
16 |
17 | {{ form.media }} 18 | {% crispy form %} 19 |
20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/edit_support_request.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Edit support request for {{ meetup.title }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block content %} 10 |
11 |
12 |
13 |

Edit support request for {{ meetup.title }}

14 |
15 |
16 |
17 |
18 | {{ form.media }} 19 | {% crispy form %} 20 |
21 |
22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/join_requests.html: -------------------------------------------------------------------------------- 1 | {% extends "meetup/base.html" %} 2 | 3 | {% block meetup_location_page_content %} 4 |
5 |

Join Requests

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% for join_request in requests %} 18 | 19 | 20 | 21 | 22 | 23 | 29 | 30 | {% endfor %} 31 | 32 |
#UserEmail addressDateAction
{{ forloop.counter }}{{ join_request.user }}{{ join_request.user.email }}{{ join_request.date_created }} 24 | Approve 26 | Reject 28 |
33 | {% include "blog/snippets/pagination.html" %} 34 |
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/list_location.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Systers Meetup Locations 5 | {% endblock %} 6 | 7 | {% block content %} 8 |

Systers Meetup Locations

9 |
10 |
11 |
12 |
13 |
14 | {% include "meetup/snippets/meetup_locations_grid.html" %} 15 |
16 |
17 | {% include "meetup/snippets/upcoming_meetups_list.html" %} 18 | {% include "meetup/snippets/systers_twitter_feed.html" %} 19 |
20 |
21 | {% include "meetup/snippets/meetup_locations_map.html" %} 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/list_support_requests.html: -------------------------------------------------------------------------------- 1 | {% extends "meetup/base.html" %} 2 | 3 | {% block meetup_page_content %} 4 |
5 |

Support Requests

6 | {% if not supportrequest_list %} 7 |
8 |

9 | No support requests to show. 10 |

11 |
12 | {% endif %} 13 | {% for support_request in supportrequest_list %} 14 |
15 |

16 | Volunteer: {{ support_request.volunteer }} 17 |

18 |

19 | {{ support_request.description|safe|truncatewords:30 }} 20 |

21 |

22 | SEE MORE 23 |

24 |
25 | {% endfor %} 26 | {% include "blog/snippets/pagination.html" %} 27 |

28 | Back to meetup 29 |

30 |
31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/location_change_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | Hello {{user}},
11 |

This is to inform you that the location for {{meetup}} has been changed to {{meetup.meetup_location}}

12 | Thank You,
13 | AnitaB.org 14 | 15 | 16 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/meetup_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | Delete {{ object.title }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 | 10 | {% url 'delete_meetup' object.slug as delete_url %} 11 | 12 |
13 |
14 |
15 |

Confirm to delete "{{ object.title }}"

16 |
17 |
18 |
19 |
20 | {% csrf_token %} 21 |

Are you sure you want to delete "{{ object.title }}"?

22 | 23 |
24 |
25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/meetup_location_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {{% extends "base.html" %} 2 | 3 | {% block title %} 4 | Delete {{ meetup_location }} 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | 9 | {% block content %} 10 |
11 |
12 |

Confirm to Delete {{ object.name }}

13 |
14 |
15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 | {% csrf_token %} 24 |

Are you sure you want to delete "{{ object.name }}"?

25 | 26 |
27 |
28 |
29 |
30 |
31 | {% include 'meetup/snippets/meetup_location_sidebar.html' %} 32 | {% block extra_sidebar %}{% endblock %} 33 |
34 |
35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/members.html: -------------------------------------------------------------------------------- 1 | {% extends "meetup/base.html" %} 2 | 3 | {% block meetup_location_page_content %} 4 |
5 | {% include "meetup/snippets/users-grid.html" with users="Moderators" user_list=moderator_list %} 6 | {% include "meetup/snippets/users-grid.html" with users="Members" user_list=member_list %} 7 |
8 | {% include "blog/snippets/pagination.html" %} 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/new_meetup_location_requests.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Unapproved Meetup Location Requests

6 | {% for meetup_location_request in object_list %} 7 |
8 |

9 | {{ meetup_location_request.name}} 10 |

11 |

12 | Requested by 13 | 14 | {{ meetup_location_request.user }} 15 | 16 |

17 | 18 |
19 | {% endfor %} 20 | {% include "blog/snippets/pagination.html" %} 21 |
22 | {% endblock %} -------------------------------------------------------------------------------- /systers_portal/templates/meetup/new_meetup_requests.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Unapproved Meetup Requests

6 | {% for meetup_request in object_list %} 7 |
8 |

9 | {{ meetup_request}} 10 |

11 |

12 | Requested by 13 | {{ meetup_request.created_by }} 14 |

15 | 16 |
17 | {% endfor %} 18 | {% include "blog/snippets/pagination.html" %} 19 |
20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/past_meetups.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Past Meetups

6 | {% for meetup in meetup_list %} 7 |
8 |

9 | {{ meetup.title }} 10 |

11 |
12 | {% endfor %} 13 | {% include "blog/snippets/pagination.html" %} 14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/reminder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | Hello {{user}},
11 |

This is a gentle reminder for {{meetup}} which will begin on {{meetup.date}} {%if meetup.time%} at {{meetup.time}} {% endif %}

12 | Thank You,
13 | AnitaB.org 14 | 15 | 16 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/request_new_meetup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - Request a Meetup 5 | {% endblock %} 6 | 7 | {% block head %} 8 | 9 | 10 | 11 | {% endblock %} 12 | 13 | {% load crispy_forms_tags %} 14 | {% block content %} 15 |
16 |
17 |
18 |

Request a meeetup(Click Here for Virtual Meetups)

19 |
20 |
21 |
22 |
23 | {{ form.media }} 24 | {% crispy form %} 25 |
26 |
27 |
28 | {% endblock %} 29 | 30 | {% block scripts %} 31 | 32 | 33 | 34 | 45 | 46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/request_new_meetup_location.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - Request a Meetup Location 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

Request a Meetup Location

13 |
14 |
15 |
16 |
17 | {{ form.media }} 18 | {% crispy form %} 19 |
20 |
21 |
22 | {% endblock %} 23 | 24 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/request_virtual_meetup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - Request a Meetup 5 | {% endblock %} 6 | 7 | {% block head %} 8 | 9 | 10 | 11 | {% endblock %} 12 | 13 | {% load crispy_forms_tags %} 14 | {% block content %} 15 |
16 |
17 |
18 |

Request a Virtual Meeetup

19 |
20 |
21 |
22 |
23 | {{ form.media }} 24 | {% crispy form %} 25 |
26 |
27 |
28 | {% endblock %} 29 | 30 | {% block scripts %} 31 | 32 | 33 | 34 | 45 | 46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/rsvp_going.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Going

6 | {% for rsvp in rsvp_list %} 7 |
8 |

9 | {% if rsvp.plus_one %} 10 | {{ rsvp.user }} + 1 11 | {% else %} 12 | {{ rsvp.user }} 13 | {% endif %} 14 |

15 |
16 | {% endfor %} 17 | {% include "blog/snippets/pagination.html" %} 18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/rsvp_meetup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load crispy_forms_tags %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

RSVP {{ meetup.title }}

10 |
11 |
12 |
13 |
14 | {{ form.media }} 15 | {% crispy form %} 16 |
17 |
18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/about_button.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/add_resource_button.html: -------------------------------------------------------------------------------- 1 | {% if user.is_authenticated and user.is_active %} 2 | {% if perms.meetup.add_resource%} 3 | 7 | {% endif %} 8 | {% endif %} 9 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/all_upcoming_meetups_list.html: -------------------------------------------------------------------------------- 1 | {% block content %} 2 |
3 | {% for meetup in meetup_list %} 4 |
5 | {{meetup.location }} 6 | 7 |
{{ meetup }}
8 |
9 | {{ meetup.date }} 10 |
11 | {% endfor %} 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/join_button.html: -------------------------------------------------------------------------------- 1 | {% if user.is_authenticated and user.is_active %} 2 | {% now "Y-m-d" as todays_date %} 3 | {% if todays_date < meetup.date|date:"Y-m-d" %} 4 | 8 | {% endif %} 9 | {% endif %} 10 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/meetup_carousel.html: -------------------------------------------------------------------------------- 1 | {% if images %} 2 |
3 |
4 | 36 |
37 | {% endif %} 38 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/meetup_location_sidebar.html: -------------------------------------------------------------------------------- 1 | {% load guardian_tags %} 2 | 3 | {% if user.is_authenticated and user.is_active %} 4 | {% get_obj_perms user for meetup_location as "meetup_location_perms" %} 5 | 26 | {% endif %} 27 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/meetup_locations_grid.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {% for location in object_list %} 4 | {{ location }} 5 |
6 | {% if forloop.counter|divisibleby:2 %} 7 |
8 |
9 |
10 | {% else %} 11 |
12 | {% endif %} 13 | {% endfor %} 14 |
15 | {% include "blog/snippets/pagination.html" %} 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/meetup_locations_map.html: -------------------------------------------------------------------------------- 1 | 2 | 26 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/share_buttons.html: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/systers_twitter_feed.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/upcoming_meetups_list.html: -------------------------------------------------------------------------------- 1 | {% block content %} 2 |

Upcoming Meetups

3 |
4 | {% for meetup in meetup_list %} 5 |
6 | {{meetup.meetup_location }} 7 | 8 |
{{ meetup }}
9 |
10 | {{ meetup.date }} 11 |
12 | {% endfor %} 13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/user-cell.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | {% load guardian_tags %} 4 | 5 | {% if user.is_authenticated and user.is_active %} 6 | {% get_obj_perms user for meetup_location as "meetup_location_perms" %} 7 | {% endif %} 8 | 9 |
10 | {% if systersuser.profile_picture.name and systersuser.profile_picture.name != "False" %} 11 | 12 | {{ systersuser }} profile picture 14 | 15 | {% else %} 16 | 17 | {{ systersuser }} profile picture 18 | 19 | {% endif %} 20 | 21 |

{{ systersuser }}

22 |
23 | {% if "delete_meetup_location_member" in meetup_location_perms %} 24 | 26 | Remove 27 | 28 | {% endif %} 29 | {% if users == "Members" and "add_meetup_location_moderator" in meetup_location_perms %} 30 | 32 | Make Moderator 33 | 34 | {% endif %} 35 | {% if users == "Moderators" and "delete_meetup_location_moderator" in meetup_location_perms %} 36 | 38 | Remove Moderator 39 | 40 | {% endif %} 41 |
42 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/snippets/users-grid.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ users }}

3 |
4 | {% for systersuser in user_list %} 5 | {% include "meetup/snippets/user-cell.html" with systersuser=systersuser %} 6 |
7 | {% if forloop.counter|divisibleby:4 %} 8 |
9 |
10 |
11 | {% else %} 12 |
13 | {% endif %} 14 | {% endfor %} 15 |
16 |
17 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/sponsors.html: -------------------------------------------------------------------------------- 1 | {% extends "meetup/base.html" %} 2 | 3 | {% block meetup_location_page_content %} 4 |
5 |

Sponsors

6 |
7 | {{ meetup_location.sponsors|safe }} 8 |
9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/support_request_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - {{ meetup }} - Delete support request 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 | 10 | {% url 'delete_support_request' meetup.slug object.pk as delete_url %} 11 | 12 |
13 |
14 |
15 |

Confirm to delete support request

16 |
17 |
18 |
19 |
20 | {% csrf_token %} 21 |

Are you sure you want to delete this support request?

22 | 23 |
24 |
25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/time_change_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | Hello {{user}},
11 |

This is to inform you that the timing for {{meetup}} has been changed to {{meetup.date}} {%if meetup.time%} {{meetup.time}} {% endif %}

12 | Thank You,
13 | AnitaB.org 14 | 15 | 16 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/unapproved_support_requests.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Unapproved Support Requests

6 | {% for support_request in supportrequest_list %} 7 |
8 |

9 | Volunteer: {{ support_request.volunteer }} 10 |

11 |

12 | {{ support_request.description|safe|truncatewords:30 }} 13 |

14 |

15 | SEE MORE 16 |

17 |

18 | Approve 20 | Reject 22 |

23 |
24 | {% endfor %} 25 | {% include "blog/snippets/pagination.html" %} 26 |

27 | Back to meetup 28 |

29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/upcoming_meetups.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |

Upcoming Meetups

6 | {% for meetup in meetup_list %} 7 |
8 | {% if meetup.meetup_picture.name and meetup.meetup_picture.name != "False" %} 9 | 10 | {{ meetup.title }} meetup picture 12 | {% endif %} 13 |

14 | {{ meetup.title }} 15 |

16 |

17 | Date: {{ meetup.date }} 18 | Time: {{ meetup.time|time:"H:i"|default:"TBA" }} 19 | Venue: {{ meetup.venue|default:"TBA" }} 20 |

21 |

22 | {{ meetup.description|safe|truncatewords:30 }} 23 | LEARN MORE 24 |

25 |
26 | {% endfor %} 27 | {% include "blog/snippets/pagination.html" %} 28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /systers_portal/templates/meetup/view_new_meetup_location_request.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

{{ meetup_location_request.name }}

5 |
6 |
7 | 8 | 9 | {% for field,value in meetup_location_request_fields %} 10 | 11 | 12 | 13 | 14 | {% endfor %} 15 | 16 |
{{ field }}{{ value }}
17 |
18 |
19 |
20 | {% if not meetup_location_request.is_approved %} 21 |
22 |
23 | Approve 25 |
26 |
27 | Reject 29 |
30 |
31 | {% endif %} 32 | {% endblock %} -------------------------------------------------------------------------------- /systers_portal/templates/meetup/view_new_meetup_request.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

{{ meetup_request.title }}

5 |
6 |
7 | 8 | 9 | {% for field,value in meetup_request_fields %} 10 | {% if value %} 11 | 12 | 13 | 14 | 15 | {% endif %} 16 | {% endfor %} 17 | 18 |
{{ field }}{{ value }}
19 |
20 |
21 |
22 | {% if not meetup_request.is_approved %} 23 |
24 |
25 | Approve 27 |
28 |
29 | Reject 31 |
32 |
33 | {% endif %} 34 | {% endblock %} 35 | -------------------------------------------------------------------------------- /systers_portal/templates/membership/transfer_ownership.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} 4 | - {{ community }} - Transfer ownership 5 | {% endblock %} 6 | 7 | {% load crispy_forms_tags %} 8 | {% block content %} 9 |
10 |
11 |
12 |

13 | Select a new {{ community }} community admin 14 |

15 |
16 |
17 |
18 |
19 | {% crispy form %} 20 |
21 |
22 |
23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/joined_meetup_location/full.txt: -------------------------------------------------------------------------------- 1 | {% url "about_meetup_location" meetup_location.slug as meetup_location_url %}You are now a Member of the meetup location {{ meetup_location }}. 2 | 3 | To view {{ meetup_location }}, click here: {{ default_http_protocol }}:8000//{{ current_site }}{{ meetup_location_url }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/joined_meetup_location/short.txt: -------------------------------------------------------------------------------- 1 | {{ meetup_location }}: {{ notice }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/made_moderator/full.txt: -------------------------------------------------------------------------------- 1 | {% url "about_meetup_location" meetup_location.slug as meetup_location_url %}You are now a Moderator of the meetup location {{ meetup_location }}. 2 | 3 | To view {{ meetup_location }}, click here: {{ default_http_protocol }}://{{ current_site }}{{ meetup_location_url }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/made_moderator/short.txt: -------------------------------------------------------------------------------- 1 | {{ meetup_location }}: {{ notice }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/new_join_request/full.txt: -------------------------------------------------------------------------------- 1 | {% url "join_requests_meetup_location" meetup_location.slug as join_requests_url %}{{ systersuser }} has requested to join {{ meetup_location }}. 2 | 3 | To view all join requests, click here: {{ default_http_protocol }}://{{ current_site }}{{ join_requests_url }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/new_join_request/short.txt: -------------------------------------------------------------------------------- 1 | {{ meetup_location }}: {{ notice }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/new_meetup/full.txt: -------------------------------------------------------------------------------- 1 | {% url "about_meetup_location" meetup_location.slug as meetup_location_url %}{% url "view_meetup" meetup_location.slug meetup.slug as meetup_url %}Meetup location {{ meetup_location }} has a new meetup: {{ meetup }}. 2 | 3 | To view {{ meetup }}, click here: {{ default_http_protocol }}://{{ current_site }}{{ meetup_url }} 4 | 5 | To view {{ meetup_location }}, click here: {{ default_http_protocol }}://{{ current_site }}{{ meetup_location_url }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/new_meetup/short.txt: -------------------------------------------------------------------------------- 1 | {{ meetup_location }}: {{ notice }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/new_meetup_request/full.txt: -------------------------------------------------------------------------------- 1 | {% url "about_meetup_location" meetup_location.slug as meetup_location_url %}{% url "view_meetup_request" meetup_location.slug meetup_request.slug as meetup_request_url %}Meetup location {{ meetup_location }} has a new meetup request: {{ meetup_request }}. 2 | 3 | To view {{ meetup_request }}, click here: {{ default_http_protocol }}://{{ current_site }}{{ meetup_request_url }} 4 | 5 | To view {{ meetup_location }}, click here: {{ default_http_protocol }}://{{ current_site }}{{ meetup_location_url }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/new_meetup_request/short.txt: -------------------------------------------------------------------------------- 1 | {{ meetup }}: {{ notice }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/new_support_request/full.txt: -------------------------------------------------------------------------------- 1 | {% url "about_meetup_location" meetup_location.slug as meetup_location_url %}{% url "view_meetup" meetup_location.slug meetup.slug as meetup_url %} 2 | {% url "view_support_request" meetup_location.slug meetup.slug support_request.pk as support_request_url %}{{ systersuser }} has added a support request to the meetup {{ meetup }} of meetup location {{ meetup_location }}. 3 | 4 | To view the support request, click here: {{ default_http_protocol }}://{{ current_site }}{{ support_request_url }} 5 | 6 | To view {{ meetup }}, click here: {{ default_http_protocol }}://{{ current_site }}{{ meetup_url }} 7 | 8 | To view {{ meetup_location }}, click here: {{ default_http_protocol }}://{{ current_site }}{{ meetup_location_url }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/new_support_request/short.txt: -------------------------------------------------------------------------------- 1 | {{ meetup }}: {{ notice }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/support_request_approved/full.txt: -------------------------------------------------------------------------------- 1 | {% url "view_meetup" meetup_location.slug meetup.slug as meetup_url %}{% url "view_support_request" meetup_location.slug meetup.slug support_request.pk as support_request_url %}Your support request for the meetup {{ meetup }} has been approved. 2 | 3 | To view the support request, click here: {{ default_http_protocol }}://{{ current_site }}{{ support_request_url }} 4 | 5 | To view {{ meetup }}, click here: {{ default_http_protocol }}://{{ current_site }}{{ meetup_url }} -------------------------------------------------------------------------------- /systers_portal/templates/pinax/notifications/support_request_approved/short.txt: -------------------------------------------------------------------------------- 1 | {{ meetup }}: {{ notice }} -------------------------------------------------------------------------------- /systers_portal/templates/socialaccount/snippets/provider_list.html: -------------------------------------------------------------------------------- 1 | {% load socialaccount %} 2 | 3 | {% for provider in socialaccount.providers %} 4 | 8 | 9 | {% endfor %} 10 | -------------------------------------------------------------------------------- /systers_portal/templates/users/edit_profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %} 3 | {% if user == systersuser.user %} 4 | - Edit my profile 5 | {% else %} 6 | - Edit profile of {{ systersuser }} 7 | {% endif %} 8 | {% endblock %} 9 | 10 | {% load crispy_forms_tags %} 11 | {% block content %} 12 |
13 |
14 |

15 | {% if user == systersuser.user %} 16 | Edit my profile 17 | {% else %} 18 | Edit profile of {{ systersuser }} 19 | {% endif %} 20 |

21 |
22 |
23 |
24 |
25 | {% crispy form %} 26 |
27 |
28 |
29 | {% endblock %} 30 | -------------------------------------------------------------------------------- /systers_portal/templates/users/pins.html: -------------------------------------------------------------------------------- 1 | {% include "base.html" %} 2 | {% block content %} 3 |

My Pins

4 | {% if pins %} 5 |
6 | {% for post in pins %} 7 |
8 |

{{ post.title }}

9 | 10 |

{{ post.date_modified }} | {{ post.author }} 12 | {% if post.resource_type %} 13 | | {{ post.resource_type }} 14 | {% endif %} 15 |

16 | 17 |
{{ post.content|safe|truncatewords:50 }}
18 | {% if post.tags %} 19 |
    20 | {% for tag in post.tags.all %} 21 |
  • {{ tag }}
  • 22 | {% endfor %} 23 |
24 | {% endif %} 25 |
26 |
27 | {% endfor %} 28 |
29 | {% else %} 30 |
31 |

No Pins

32 |
33 | 34 | {% endif %} 35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /systers_portal/templates/users/settings.html: -------------------------------------------------------------------------------- 1 | {% include "base.html" %} 2 | {% block content%} 3 | {% load crispy_forms_tags %} 4 |
5 |

Email Subscription Settings

6 |
7 |
8 |
9 | {%crispy form%} 10 |
11 | {%endblock%} 12 | -------------------------------------------------------------------------------- /systers_portal/templates/users/snippets/permissions.html: -------------------------------------------------------------------------------- 1 |
2 |
Permissions
3 |
4 | {% if permission_groups %} 5 | 6 | 7 | {% for group in permission_groups %} 8 | 9 | 10 | 11 | {% endfor %} 12 | 13 |
{{ group }}
14 | {% else %} 15 |

Looks like you have no permissions.

16 | {% endif %} 17 |
18 |
19 | -------------------------------------------------------------------------------- /systers_portal/templates/users/snippets/profile.html: -------------------------------------------------------------------------------- 1 | {% load verbose_name %} 2 |
3 |
Profile
4 |
5 | {% if systersuser.profile_picture.name and systersuser.profile_picture.name != "False" %} 6 | {{ systersuser }} profile picture 10 |
11 | {% endif %} 12 |

13 | 14 | {{ systersuser.user.email|urlize }} 15 |

16 | {% if systersuser.country %} 17 |

18 | 19 | {{ systersuser.country.name }} 20 |

21 | {% endif %} 22 | {% for name, value in systersuser.get_fields %} 23 | {% with "blog_url homepage_url" as profile_fields %} 24 | {% if name in profile_fields and value %} 25 |

{% verbose_name systersuser name %}: {{ value|urlize }}

26 | {% endif %} 27 | {% endwith %} 28 | {% endfor %} 29 | {% if user == systersuser.user or user.is_superuser %} 30 |
31 | Edit profile 33 |
34 | {% endif %} 35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /systers_portal/templates/users/view_profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %} - Profile of {{ systersuser }}{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

{{ systersuser }}

10 |
11 |
12 |
13 | {% include "users/snippets/profile.html" %} 14 |
15 |
16 | {% include "users/snippets/membership.html" %} 17 |
18 |
19 | {% include "users/snippets/permissions.html" %} 20 |
21 |
22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /systers_portal/users/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'users.apps.UsersConfig' 2 | -------------------------------------------------------------------------------- /systers_portal/users/adapter.py: -------------------------------------------------------------------------------- 1 | from allauth.account.adapter import DefaultAccountAdapter 2 | from django.urls import reverse 3 | from django.core.exceptions import ValidationError 4 | import re 5 | 6 | 7 | class SystersUserAccountAdapter(DefaultAccountAdapter): 8 | """Custom account adapter with different than default redirect URLs""" 9 | 10 | def clean_username(self, username, shallow=False): 11 | if len(username) < 3: 12 | raise ValidationError("Username must be atleast 3 characters long") 13 | return username 14 | 15 | def clean_password(self, password, user=None): 16 | # Password should have at least one uppercase letter, one digit and one special character 17 | x = r'^(?=.*?[A-Z])' 18 | y = '[~!@#$%^&*()_+{}":;\']+$' 19 | z = r'^(?=.*?[0-9])' 20 | digit = re.match(z, password) 21 | special_char = set(y).intersection(password) 22 | uppercase = re.match(x, password) 23 | if len(password) >= 6 and digit and uppercase and special_char: 24 | return password 25 | else: 26 | raise ValidationError( 27 | "Password must have at least, 6 characters, one uppercase letter, " 28 | "one special character and one digit.") 29 | 30 | def get_login_redirect_url(self, request): 31 | return reverse('user', args=[request.user.username]) 32 | 33 | def get_signup_redirect_url(self, request): 34 | return reverse('user', args=[request.user.username]) 35 | -------------------------------------------------------------------------------- /systers_portal/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from users.models import SystersUser, UserSetting 3 | 4 | 5 | admin.site.register(SystersUser) 6 | admin.site.register(UserSetting) 7 | -------------------------------------------------------------------------------- /systers_portal/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.conf import settings 3 | 4 | 5 | class UsersConfig(AppConfig): 6 | name = 'users' 7 | 8 | def ready(self): 9 | import users.signals # noqa # pylint: disable=unused-variable 10 | from . import scheduler 11 | if settings.SCHEDULER_AUTOSTART: 12 | scheduler.start() 13 | -------------------------------------------------------------------------------- /systers_portal/users/migrations/0002_auto_20200825_1932.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.9 on 2020-08-25 19:32 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('users', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='usersetting', 16 | name='user', 17 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.SystersUser'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /systers_portal/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/users/migrations/__init__.py -------------------------------------------------------------------------------- /systers_portal/users/signals.py: -------------------------------------------------------------------------------- 1 | from django.db.models.signals import post_save 2 | from django.dispatch import receiver 3 | 4 | from users.models import SystersUser, UserSetting 5 | 6 | 7 | @receiver(post_save, sender=SystersUser, dispatch_uid="create_settings") 8 | def create_user_settings(sender, instance, created, **kwargs): 9 | if created: 10 | UserSetting.objects.create(user=instance) 11 | -------------------------------------------------------------------------------- /systers_portal/users/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anitab-org/portal/217cfeb6e2e09843994eef8887d5d345a9680bab/systers_portal/users/tests/__init__.py -------------------------------------------------------------------------------- /systers_portal/users/tests/test_signals.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.db.models.signals import post_save 3 | from django.test import TestCase 4 | 5 | from users.models import SystersUser, UserSetting 6 | from users.signals import create_user_settings 7 | 8 | 9 | class UserSettingsSignalsTestCase(TestCase): 10 | def setUp(self): 11 | post_save.connect(create_user_settings, sender=SystersUser, 12 | dispatch_uid="create_settings") 13 | self.password = "foobar" 14 | 15 | def test_settings_signal(self): 16 | user = User.objects.create_user(username='foo', password=self.password, 17 | email='user@test.com') 18 | systers_user = SystersUser.objects.get(user=user) 19 | settings = UserSetting.objects.filter(user=systers_user) 20 | self.assertEqual(settings.count(), 1) 21 | self.assertEqual(settings.first().weekly_digest, True) 22 | self.assertEqual(settings.first().location_change, False) 23 | -------------------------------------------------------------------------------- /systers_portal/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from users.views import UserView, UserProfileView 4 | from users.views import EditSettings, UserPinsListView 5 | 6 | from blog.views import RemovePinFromListView 7 | 8 | urlpatterns = [ 9 | url(r'^(?P[\w.@+-]+)/$', UserView.as_view(), name='user'), 10 | url(r'^(?P[\w.@+-]+)/profile/$', UserProfileView.as_view(), 11 | name='user_profile'), 12 | url(r'^(?P[\w.@+-]+)/settings/$', EditSettings.as_view(), 13 | name="edit_settings"), 14 | url(r'^(?P[\w.@+-]+)/mypins/$', UserPinsListView.as_view(), 15 | name="user_pins"), 16 | url(r'^(?P[\w.@+-]+)/mypins/unpin.$', RemovePinFromListView.as_view(), 17 | name="user_unpin") 18 | ] 19 | --------------------------------------------------------------------------------