Code Monkey home page Code Monkey logo

linkedevents's Introduction

Linked Events

Build status codecov Requirements Gitter

High-level diagram of Linked Events

TL;DR => Linked Events is a REST API which allows you to set up an event* publication hub.

*event here means a happening where people get together and do something.

Linked Events is event information:

  • Aggregator => using Python importers which have the logic to import events information from different data sources
  • Creator => by offering PUT/POST /event API endpoint with granular user permissions and a hierarchical organization structure supporting different publishing rights for different organizations
  • Publisher => by offering API endpoints from which interested parties can retrieve information about events

Linked Events was originally developed for City of Helsinki organization and you can see the Linked Events API in action for Helsinki capital region here. It contains data from all Helsinki City Departments as well as data from Helsinki Marketing and the Helmet metropolitan area public libraries. Viewing the API should give a reasonable view for the kind of information Linked Events is targeted for.

Target audience of this README.md are developers who may or maynot have a lot of Python experience and would like to get things running as quickly as possible. Therefore, instructions written in this README.md should be written accordingly.

Contributing

The best way to contribute is to open a new PR for discussion. We strive to be able to support various cities with various use cases, so suggestions and new features (as long as they fit in with existing functionality) are welcome.

How to setup your local development environment

If all you want is a barebone application to work with for your own city:

  • Copy ./docker/django/.env.example to ./docker/django/.env and change the variable values to your liking.

  • Start django application and database server:

    docker-compose up
    
  • Access application on localhost:8000

  • You are done 🔥

If you wish to use locations, addresses and events data from the Helsinki capital region:

  • Read linked-events-importers.md and decide the importers or commands you would like to use.

  • You can then serve the UI for Linked Events API for example by setting authentication keys to local_settings.py

  • UI app is specific to Helsinki at the moment and requires general Finnish ontology as well as additional Helsinki specific audiences and keywords to be present. However, UI code should be easily adaptable to your own city if you have an OAuth2 authentication server present

Development installation on physical or virtual machine

These instructions assume an $INSTALL_BASE, like so:

INSTALL_BASE=$HOME/linkedevents

If you've already cloned this repository, just move repository root into $INSTALL_BASE/linkedevents. Otherwise just clone the repository, like so:

git clone https://github.com/City-of-Helsinki/linkedevents.git $INSTALL_BASE/linkedevents

Prepare Python 3.x virtualenv using your favorite tools and activate it. Plain virtualenv is like so:

virtualenv -p python3 $INSTALL_BASE/venv
source $INSTALL_BASE/venv/bin/activate

Install required Python packages into the virtualenv

cd $INSTALL_BASE/linkedevents
pip install -r requirements.txt

Create the database, like so: (we have only tested on PostgreSQL)

cd $INSTALL_BASE/linkedevents
sudo -u postgres createuser -R -S linkedevents
# Following is for US locale, we are not certain whether Linkedevents
# behaves differently depending on DB collation & ctype
#sudo -u postgres createdb -Olinkedevents linkedevents
# This is is for Finnish locale
sudo -u postgres createdb -Olinkedevents -Ttemplate0 -lfi_FI.UTF-8 linkedevents
# Create extensions in the database
sudo -u postgres psql linkedevents -c "CREATE EXTENSION postgis;"
sudo -u postgres psql linkedevents -c "CREATE EXTENSION hstore;"
# This fills the database with a basic skeleton
python manage.py migrate
# This adds language fields based on settings.LANGUAGES (which may be missing in external dependencies)
python manage.py sync_translation_fields
# This creates language objects with correct translations
python manage.py create_languages

If you wish to install Linkedevents without any Helsinki specific data (an empty database), and instead customize everything for your own city, you have a working install right now.

The last steps are needed if you wish to use location, address or event data from the Helsinki metropolitan region, or if you wish to run the Helsinki UI (https://linkedevents.hel.fi) from https://github.com/City-of-Helsinki/linkedevents-ui. Currently, the UI is specific to Helsinki and requires the general Finnish ontology as well as additional Helsinki specific audiences and keywords to be present, though its code should be easily adaptable to your own city if you have an OAuth2 authentication server present.

The commands below are documented in more detail in linked-events-importers.md.

cd $INSTALL_BASE/linkedevents
# Import general Finnish ontology (used by Helsinki UI and Helsinki events)
python manage.py event_import yso --all
# Add keyword set to display in the UI event audience selection
python manage.py add_helsinki_audience
# Add keyword set to display in the UI main category selection
python manage.py add_helsinki_topics
# Import places from Helsinki metropolitan region service registry (used by events from following sources)
python manage.py event_import tprek --places
# Import places from Helsinki metropolitan region address registry (used as fallback locations)
python manage.py event_import osoite --places
# Import events from Helsinki metropolitan region libraries
python manage.py event_import helmet --events
# Import events from Espoo
python manage.py event_import espoo --events
# Import City of Helsinki hierarchical organization for UI user rights management
python manage.py import_organizations https://dev.hel.fi/paatokset/v1/organization/?limit=1000 -c openahjo -s OpenAhjoAPI:ahjo
# Import municipalities in Finland
python manage.py geo_import finland --municipalities
# Import districts in Helsinki
python manage.py geo_import helsinki --divisions
# install API frontend templates:
python manage.py install_templates helevents

The last command installs the helevents/templates/rest_framework/api.html template, which contains Helsinki event data summary and license. You may customize the template for your favorite city by creating your_favorite_city/templates/rest_framework/api.html. For further erudition, take a look at the DRF documentation on customizing the browsable API

After this, everything but search endpoint (/search) is working. See search

Development in macOS (linux/arm64)

  • Note: Processor architecture on Apple-silicon machines is ARM64, not AMD64. If you have one of these extra tinkering is needed. On Intel-silicon machines, this is not necessary.
  • Note 2: As AMD64 is emulated on top of ARM64 Linux, this isn't fast.
  1. Pre-build linux/AMD64-versions
    1. PostgreSQL-container:
      podman build --platform=linux/amd64 \
        -f docker/postgres/Dockerfile  \
        .
    2. Django-container:
      podman build --platform=linux/amd64 \
        -f docker/postgres/Dockerfile  \
        .
  2. Now podman-compose will find pre-built alternate architecture container images and will run ok:
    podman-compose up
  3. Done, test @ http://127.0.0.1:8000/

GIS

  • Problem on macOS:
    django.contrib.gis.geos.error.GEOSException: Error encountered checking Geometry returned from GEOS C function "GEOSGeom_createCollection_r".
    
  • Fix:
    • Edit linkedevents/settings.py, add to end:
      # macOS:
      GDAL_LIBRARY_PATH="/opt/homebrew/opt/gdal/lib/libgdal.dylib"
      GEOS_LIBRARY_PATH="/opt/homebrew/opt/geos/lib/libgeos_c.dylib"
      

Troubleshooting

  • Problem: Error: no container with name or ID "linkedevents-backend" found: no such container

    • Solution: You missed the part with: Copy ./docker/django/.env.example to ./docker/django/.env
  • Problem: TCP-ports not available in host

    • Solution: If port forwarding from VM is flaky, native ports are visible. Django should be visible in http://127.0.0.1:8080/

Production notes

Development installation above will give you quite a serviceable production installation for lightish usage. You can serve out the application using your favorite WSGI-capable application server. The WSGI-entrypoint for Linked Events is linkedevents.wsgi or in file linkedevents/wsgi.py. Former is used by gunicorn, latter by uwsgi. The callable is application.

You will also need to serve out static and media folders at /static and /media in your URL space.

Running tests

Tests must be run using an user who can create (and drop) databases and write the directories your linkedevents installation resides in. Also the template database must include Postgis and HSTORE-extensions. If you are developing, you probably want to give those permissions to the database user configured in your development instance. Like so:

# Change this if you have different DB user
DATABASE_USER=linkedevents
# Most likely you have a postgres system user that can log into postgres as DB postgres user
sudo -u postgres psql << EOF
ALTER USER "$DATABASE_USER" CREATEDB;
\c template1
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
EOF

Afterwards you can run the tests:

cd $INSTALL_BASE/linkedevents
py.test events

Note that search tests will fail unless you configure search

Sending email

The project uses django-mailer for queuing and sending email. When a normal Django email function like send_mail is called, the email will be stored into a queue that exists in the database for later sending.

django-mailer comes with a number of management commands for interacting with the email queue. Out of those, the send_mail command is for us perhaps the most notable one as it is responsible for the actual sending of the queued messages.

To send messages locally, you can run the send_mail command: ./manage.py send_mail

For more information about django-mailer and the management commands, you can refer to the usage documentation.

Requirements

Linked Events uses two files for requirements. The workflow is as follows.

requirements.txt is not edited manually, but is generated with pip-compile.

requirements.txt always contains fully tested, pinned versions of the requirements. requirements.in contains the primary, unpinned requirements of the project without their dependencies.

In production, deployments should always use requirements.txt and the versions pinned therein. In development, new virtualenvs and development environments should also be initialised using requirements.txt. pip-sync will synchronize the active virtualenv to match exactly the packages in requirements.txt.

In development and testing, to update to the latest versions of requirements, use the command pip-compile. You can use requires.io to monitor the pinned versions for updates.

To remove a dependency, remove it from requirements.in, run pip-compile and then pip-sync. If everything works as expected, commit the changes.

Code format

This project uses black, flake8 and isort for code formatting and quality checking. Project follows the basic black config, without any modifications.

Basic black commands:

  • To let black do its magic: black .
  • To see which files black would change: black --check .

pre-commit can be used to install and run all the formatting tools as git hooks automatically before a commit.

Git blame ignore refs

Project includes a .git-blame-ignore-revs file for ignoring certain commits from git blame. This can be useful for ignoring e.g. formatting commits, so that it is more clear from git blame where the actual code change came from. Configure your git to use it for this project with the following command:

git config blame.ignoreRevsFile .git-blame-ignore-revs

Commit message format

New commit messages must adhere to the Conventional Commits specification, and line length is limited to 72 characters.

When pre-commit is in use, commitlint checks new commit messages for the correct format.

Search

Linkedevents uses Elasticsearch for generating results on the /search-endpoint. If you wish to use that functionality, proceed like so:

  1. Install elasticsearch

    We've only tested using the rather ancient 1.7 version. If you are using Ubuntu 16.04, 1.7 will be available in the official repository. This limitation was originally due to django-haystack not supporting versions above 1. As of writing this the django-haystack version in use does support versions 1, 2, 5 and 7.

  2. (For Finnish support) Install elasticsearch-analyzer-voikko, libvoikko and needed dictionaries

    /usr/share/elasticsearch/bin/plugin -i fi.evident.elasticsearch/elasticsearch-analysis-voikko/0.4.0 This specific command is for Debian derivatives. The path to plugin command might be different on yours. Note that version 0.4.0 is the one compatible with Elasticsearch 1.7

    Installing libvoikko: apt-get install libvoikko1

    Installing the dictionaries (v5 dictionaries are needed for libvoikko version included in Ubuntu 16.04):

   wget -P $INSTALL_BASE http://www.puimula.org/htp/testing/voikko-snapshot-v5/dict-morpho.zip    unzip $INSTALL_BASE/dict-morpho.zip -d /etc/voikko ```

  1. Configure the thing

    Set the ELASTICSEARCH_URL environment variable (or variable in config_dev.env, if you are running in development mode) to your elasticsearch instance. The default value is http://localhost:9200/.

    Haystack configuration for all Linkedevents languages happens automatically if ELASTICSEARCH_URL is set, but you may customize it manually using local_settings.py if you know Haystack and wish to do so.

  2. Rebuild the search indexes

  python manage.py rebuild_index

  You should now have a working /search endpoint, give or take a few.

Event extensions

It is possible to extend event data and API without touching events application by implementing separate extension applications. These extensions will be wired under field extension_<extension idenfier> in event API. If not auto enabled (see 6. below), extensions can be enabled per request using query param extensions with comma separated identifiers as values, or all for enabling all the extensions.

To implement an extension:

  1. Create a new Django application, preferably named extension_<unique identifier for the extension>.

  2. If you need to add new data for events, implement that using model(s) in the extension application.

  3. Inherit events.extensions.EventExtension and implement needed attributes and methods. See extensions.py for details.

  4. Add event_extension: <your EventExtension subclass> attribute to the extension applications's AppConfig.

  5. Make the extension available by adding the extension application to INSTALLED_APPS.

  6. If you want to force the extension to be enabled on every request, add the extension's identifier to AUTO_ENABLED_EXTENSIONS in Django settings.

For an example extension implementation, see course extension.

linkedevents's People

Contributors

aapris avatar akx avatar aleksisalonen avatar anttileppa avatar cap-jatu avatar charn avatar danipran avatar dankiki avatar dependabot[bot] avatar harriris-vincit avatar hkurhinen avatar hugovk avatar hukka avatar igordavydsson avatar ilmoeuro avatar jorilindell avatar jussiarpalahti avatar juyrjola avatar mingfeng avatar nicobav avatar rikuoja avatar sam-hosseini avatar terovirtanen avatar tituomin avatar tonipel avatar tuhola avatar tuomas777 avatar vikoivun avatar voneiden avatar zenbum avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

linkedevents's Issues

Event list should support multiple location and keyword filters

Currently you can filter events by keyword id by adding one keyword parameter, e.g. keyword=yso:p22439
http://api.hel.fi/linkedevents/v0.1/event/?end=&start=2014-10-07&keyword=yso:p22439

It would be very useful, if event list supported filtering by multiple keywords and locations. I see at least to possible formats:

  1. Multiple keyword parameters:
    keyword=yso:p22439&keyword=yso:p12345

  2. Multiple comma separated ids in a keyword parameter:
    keyword=yso:p22439,yso:p12345

Search returns data in result list instead of data

Search List return Events in a list named result while it should be a list named data.
Example URL: http://api.hel.fi/linkedevents/v0.1/search/?q=uni

GET /linkedevents/v0.1/search/?q=uni
HTTP 200 OK
Content-Type: application/json ;utf-8
Allow: GET, HEAD, OPTIONS
Vary: Accept

{
    "count": 23,
    "next": "http://api.hel.fi/linkedevents/v0.1/search/?q=uni&page=2",
    "previous": null,
    "results": [
        {
...

It should be:

HTTP 200 OK
Content-Type: application/json ;utf-8
Allow: GET, HEAD, OPTIONS
Vary: Accept

{
    "data": [
        {
...

I think this could be fixed by adding
pagination_serializer_class = CustomPaginationSerializer
to the beginning of

class SearchViewSet(GeoModelAPIView, viewsets.ViewSetMixin, generics.ListAPIView):
    serializer_class = SearchSerializer
    pagination_serializer_class = CustomPaginationSerializer

in file events/api.py.
https://github.com/City-of-Helsinki/linkedevents/blob/master/events/api.py#L610

NOTE This will break all applications which rely on result list.

Deduplicate events at import

For example kulke:31042 and matko:21991 are the same event.

Kulke is the primary data source for events at kulke managed locations. The details of the deduplication should be thought out.

Event namespace should be configurable

Currently newly POSTed Events get id in format namespace:sOm3Rand0mC4ar5. Event namespace is hardcoded in the beginning of file events/api.py:

SYSTEM_DATA_SOURCE_ID = 'system'

This namespace value should be configurable (e.g. in local_settings.py), so that different instances of Linked event APIs could use custom namespace to avoid collisions in ids and for better separation of originating Linked event instance, where the Event was created.

event_import yso --keywords fails

Importing YSO keywords fails, if old enough yso.sqlite doesn't exist in the root directory. Probably YSO data file has changed and importer can't handle this new data.

python manage.py event_import yso --keywords
produces traceback

Importing YSO keywords
Traceback (most recent call last):
  File "manage.py", line 13, in <module>
    execute_from_command_line(sys.argv)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/management/__init__.py", line 399, in execute_from_command_line
    utility.execute()
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/management/__init__.py", line 392, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/management/base.py", line 242, in run_from_argv
    self.execute(*args, **options.__dict__)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/management/base.py", line 285, in execute
    output = self.handle(*args, **options)
  File ".../linkedevents/events/management/commands/event_import.py", line 61, in handle
    method()
  File ".../linkedevents/events/importer/yso.py", line 48, in import_keywords
    self.save_keywords(graph)
  File ".../linkedevents/events/importer/yso.py", line 104, in save_keywords
    self.save_keywords_in_bulk(graph, data_source)
  File ".../linkedevents/events/importer/yso.py", line 152, in save_keywords_in_bulk
    with active_language(literal.language):
  File ".../linkedevents/events/importer/util.py", line 41, in __enter__
    activate(self.language)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/utils/translation/trans_real.py", line 188, in activate
    _active.value = translation(language)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/utils/translation/trans_real.py", line 178, in translation
    current_translation = _fetch(language, fallback=default_translation)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/utils/translation/trans_real.py", line 128, in _fetch
    loc = to_locale(lang)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/utils/translation/trans_real.py", line 55, in to_locale
    p = language.find('-')
AttributeError: 'NoneType' object has no attribute 'find'

Linked events is not Django REST framework 3.0 compatible

Linked events is currently not Django REST framework 3.0 compatible. After upgrading DRF from 2.x->3.0.1 attempt to start dev server raises this:

$ python manage.py runserver
Validating models...

Unhandled exception in thread started by <function check_errors.<locals>.wrapper at 0x107d4e488>
Traceback (most recent call last):
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/urlresolvers.py", line 358, in urlconf_module
    return self._urlconf_module
AttributeError: 'RegexURLResolver' object has no attribute '_urlconf_module'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/utils/autoreload.py", line 93, in wrapper
    fn(*args, **kwargs)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/management/commands/runserver.py", line 102, in inner_run
    self.validate(display_num_errors=True)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/management/base.py", line 310, in validate
    num_errors = get_validation_errors(s, app)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/management/validation.py", line 34, in get_validation_errors
    for (app_name, error) in get_app_errors().items():
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/db/models/loading.py", line 196, in get_app_errors
    self._populate()
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/db/models/loading.py", line 75, in _populate
    self.load_app(app_name, True)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/db/models/loading.py", line 99, in load_app
    models = import_module('%s.models' % app_name)
  File ".../virtualenv/linkedevents3/lib/python3.4/importlib/__init__.py", line 109, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 2254, in _gcd_import
  File "<frozen importlib._bootstrap>", line 2237, in _find_and_load
  File "<frozen importlib._bootstrap>", line 2226, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 1200, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 1129, in _exec
  File "<frozen importlib._bootstrap>", line 1471, in exec_module
  File "<frozen importlib._bootstrap>", line 321, in _call_with_frames_removed
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/debug_toolbar/models.py", line 9, in <module>
    dt_settings.patch_all()
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/debug_toolbar/settings.py", line 215, in patch_all
    patch_root_urlconf()
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/debug_toolbar/settings.py", line 203, in patch_root_urlconf
    reverse('djdt:render_panel')
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/urlresolvers.py", line 507, in reverse
    app_list = resolver.app_dict[ns]
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/urlresolvers.py", line 329, in app_dict
    self._populate()
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/urlresolvers.py", line 267, in _populate
    for pattern in reversed(self.url_patterns):
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/urlresolvers.py", line 365, in url_patterns
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/core/urlresolvers.py", line 360, in urlconf_module
    self._urlconf_module = import_module(self.urlconf_name)
  File ".../virtualenv/linkedevents3/lib/python3.4/importlib/__init__.py", line 109, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 2254, in _gcd_import
  File "<frozen importlib._bootstrap>", line 2237, in _find_and_load
  File "<frozen importlib._bootstrap>", line 2226, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 1200, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 1129, in _exec
  File "<frozen importlib._bootstrap>", line 1471, in exec_module
  File "<frozen importlib._bootstrap>", line 321, in _call_with_frames_removed
  File ".../linkedevents/demoform/urls.py", line 11, in <module>
    from linkedevents.urls import urlpatterns as urlpatterns2
  File ".../linkedevents/linkedevents/urls.py", line 8, in <module>
    url(r'^v0.1/', include('events.urls')),
  File ".../virtualenv/linkedevents3/lib/python3.4/site-packages/django/conf/urls/__init__.py", line 26, in include
    urlconf_module = import_module(urlconf_module)
  File ".../virtualenv/linkedevents3/lib/python3.4/importlib/__init__.py", line 109, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File ".../linkedevents/events/urls.py", line 3, in <module>
    from events import api
  File ".../linkedevents/events/api.py", line 108, in <module>
    class EnumChoiceField(serializers.WritableField):
AttributeError: 'module' object has no attribute 'WritableField'

See http://www.django-rest-framework.org/api-guide/fields/#custom-fields
"Note that the WritableField class that was present in version 2.x no longer exists. You should subclass Field and override to_internal_value() if the field supports data input."

README.md doesn't explain what the project is and what it does

It's hard to get citizens to participate if the project READMEs don't contain a broad outline of what each project does for the city, and how it's connected to the overall architecture.

We don't need hundred-page documents or sheafs of UML diagrams, just a simple high level explanation in a few sentences.

Add separate fields for kulke caption and body text.

Kulke needs them separately, so maybe the hstore custom_fields could contain these. Only problem, hstore doesn't supported nested values, so the different language versions should be flattened out like this:

caption_fi: ...
caption_sv: ...

Instead of:

caption: {'fi': ..., 'sv': ...}

We should still keep the combined description field as it is.

Should we switch from hstore to a json field?

Don't import courses for kulke

Courses can be spotted by looking at the categories: if the event contains type 5 categories, it is a course. Attendance is restricted, so the event data could be confusing if published to the general public.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.