From 75430de2e0033f6a4d4d7830a92ad3c52bdc615d Mon Sep 17 00:00:00 2001 From: Markos Gogoulos Date: Tue, 15 Dec 2020 23:33:43 +0200 Subject: [PATCH] MediaCMS backend, initial commit --- AUTHORS.txt | 4 + LICENSE.txt | 661 +++++++ README.md | 147 ++ actions/__init__.py | 0 actions/admin.py | 0 actions/apps.py | 5 + actions/migrations/0001_initial.py | 54 + actions/migrations/0002_mediaaction_media.py | 26 + actions/migrations/0003_auto_20201201_0712.py | 42 + actions/migrations/__init__.py | 0 actions/models.py | 55 + actions/tests.py | 0 actions/views.py | 0 cms/__init__.py | 4 + cms/celery.py | 16 + cms/custom_pagination.py | 29 + cms/permissions.py | 64 + cms/settings.py | 437 +++++ cms/urls.py | 13 + cms/wsgi.py | 7 + deploy/celery_beat.service | 24 + deploy/celery_long.service | 31 + deploy/celery_short.service | 41 + deploy/mediacms.io | 78 + deploy/mediacms.io_fullchain.pem | 58 + deploy/mediacms.io_privkey.pem | 28 + deploy/mediacms.service | 13 + deploy/mediacms_logrorate | 7 + deploy/nginx.conf | 41 + deploy/uwsgi.ini | 19 + deploy/uwsgi_params | 16 + docs/Configuration.md | 244 +++ docs/User_Scenarios.md | 20 + docs/images/embed.jpg | Bin 0 -> 126732 bytes docs/images/index.jpg | Bin 0 -> 200831 bytes docs/images/video.jpg | Bin 0 -> 159849 bytes files/__init__.py | 0 files/admin.py | 87 + files/apps.py | 5 + files/backends.py | 77 + files/context_processors.py | 40 + files/exceptions.py | 4 + files/feeds.py | 26 + files/forms.py | 95 + files/helpers.py | 754 ++++++++ files/management_views.py | 195 ++ files/methods.py | 437 +++++ files/migrations/0001_initial.py | 637 ++++++ files/migrations/0002_auto_20201201_0712.py | 240 +++ files/migrations/__init__.py | 0 files/models.py | 1714 +++++++++++++++++ files/permissions.py | 9 + files/serializers.py | 257 +++ files/stop_words.py | 86 + files/tasks.py | 851 ++++++++ files/tests.py | 0 files/urls.py | 91 + files/views.py | 1273 ++++++++++++ fixtures/categories.json | 1 + fixtures/encoding_profiles.json | 1 + install.sh | 123 ++ manage.py | 15 + requirements.txt | 32 + uploader/__init__.py | 1 + uploader/apps.py | 6 + uploader/fineuploader.py | 96 + uploader/forms.py | 19 + uploader/models.py | 1 + uploader/urls.py | 10 + uploader/utils.py | 22 + uploader/views.py | 76 + users/__init__.py | 0 users/adapter.py | 22 + users/admin.py | 38 + users/apps.py | 5 + users/forms.py | 59 + users/migrations/0001_initial.py | 283 +++ users/migrations/__init__.py | 0 users/models.py | 220 +++ users/serializers.py | 82 + users/tests.py | 0 users/urls.py | 43 + users/validators.py | 18 + users/views.py | 296 +++ uwsgi.ini | 27 + 85 files changed, 10558 insertions(+) create mode 100644 AUTHORS.txt create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 actions/__init__.py create mode 100644 actions/admin.py create mode 100644 actions/apps.py create mode 100644 actions/migrations/0001_initial.py create mode 100644 actions/migrations/0002_mediaaction_media.py create mode 100644 actions/migrations/0003_auto_20201201_0712.py create mode 100644 actions/migrations/__init__.py create mode 100644 actions/models.py create mode 100644 actions/tests.py create mode 100644 actions/views.py create mode 100644 cms/__init__.py create mode 100644 cms/celery.py create mode 100644 cms/custom_pagination.py create mode 100644 cms/permissions.py create mode 100644 cms/settings.py create mode 100644 cms/urls.py create mode 100644 cms/wsgi.py create mode 100644 deploy/celery_beat.service create mode 100644 deploy/celery_long.service create mode 100644 deploy/celery_short.service create mode 100644 deploy/mediacms.io create mode 100644 deploy/mediacms.io_fullchain.pem create mode 100644 deploy/mediacms.io_privkey.pem create mode 100644 deploy/mediacms.service create mode 100644 deploy/mediacms_logrorate create mode 100644 deploy/nginx.conf create mode 100644 deploy/uwsgi.ini create mode 100644 deploy/uwsgi_params create mode 100644 docs/Configuration.md create mode 100644 docs/User_Scenarios.md create mode 100644 docs/images/embed.jpg create mode 100644 docs/images/index.jpg create mode 100644 docs/images/video.jpg create mode 100644 files/__init__.py create mode 100644 files/admin.py create mode 100644 files/apps.py create mode 100644 files/backends.py create mode 100644 files/context_processors.py create mode 100644 files/exceptions.py create mode 100644 files/feeds.py create mode 100644 files/forms.py create mode 100644 files/helpers.py create mode 100644 files/management_views.py create mode 100644 files/methods.py create mode 100644 files/migrations/0001_initial.py create mode 100644 files/migrations/0002_auto_20201201_0712.py create mode 100644 files/migrations/__init__.py create mode 100644 files/models.py create mode 100644 files/permissions.py create mode 100644 files/serializers.py create mode 100644 files/stop_words.py create mode 100644 files/tasks.py create mode 100644 files/tests.py create mode 100644 files/urls.py create mode 100644 files/views.py create mode 100644 fixtures/categories.json create mode 100644 fixtures/encoding_profiles.json create mode 100644 install.sh create mode 100755 manage.py create mode 100644 requirements.txt create mode 100644 uploader/__init__.py create mode 100644 uploader/apps.py create mode 100644 uploader/fineuploader.py create mode 100644 uploader/forms.py create mode 100644 uploader/models.py create mode 100644 uploader/urls.py create mode 100644 uploader/utils.py create mode 100644 uploader/views.py create mode 100644 users/__init__.py create mode 100644 users/adapter.py create mode 100644 users/admin.py create mode 100644 users/apps.py create mode 100644 users/forms.py create mode 100644 users/migrations/0001_initial.py create mode 100644 users/migrations/__init__.py create mode 100644 users/models.py create mode 100644 users/serializers.py create mode 100644 users/tests.py create mode 100644 users/urls.py create mode 100644 users/validators.py create mode 100644 users/views.py create mode 100644 uwsgi.ini diff --git a/AUTHORS.txt b/AUTHORS.txt new file mode 100644 index 0000000..b76761c --- /dev/null +++ b/AUTHORS.txt @@ -0,0 +1,4 @@ +Wordgames.gr - https://www.wordgames.gr +Yiannis Stergiou - ys.stergiou@gmail.com +Markos Gogoulos - mgogoulos@gmail.com + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..de81495 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,661 @@ +GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + +The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + +0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +1. Source Code. + +The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + +The Corresponding Source for a work in source code form is that +same work. + +2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + +4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + +a) The work must carry prominent notices stating that you modified +it, and giving a relevant date. + +b) The work must carry prominent notices stating that it is +released under this License and any conditions added under section +7. This requirement modifies the requirement in section 4 to +"keep intact all notices". + +c) You must license the entire work, as a whole, under this +License to anyone who comes into possession of a copy. This +License will therefore apply, along with any applicable section 7 +additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no +permission to license the work in any other way, but it does not +invalidate such permission if you have separately received it. + +d) If the work has interactive user interfaces, each must display +Appropriate Legal Notices; however, if the Program has interactive +interfaces that do not display Appropriate Legal Notices, your +work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + +a) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by the +Corresponding Source fixed on a durable physical medium +customarily used for software interchange. + +b) Convey the object code in, or embodied in, a physical product +(including a physical distribution medium), accompanied by a +written offer, valid for at least three years and valid for as +long as you offer spare parts or customer support for that product +model, to give anyone who possesses the object code either (1) a +copy of the Corresponding Source for all the software in the +product that is covered by this License, on a durable physical +medium customarily used for software interchange, for a price no +more than your reasonable cost of physically performing this +conveying of source, or (2) access to copy the +Corresponding Source from a network server at no charge. + +c) Convey individual copies of the object code with a copy of the +written offer to provide the Corresponding Source. This +alternative is allowed only occasionally and noncommercially, and +only if you received the object code with such an offer, in accord +with subsection 6b. + +d) Convey the object code by offering access from a designated +place (gratis or for a charge), and offer equivalent access to the +Corresponding Source in the same way through the same place at no +further charge. You need not require recipients to copy the +Corresponding Source along with the object code. If the place to +copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) +that supports equivalent copying facilities, provided you maintain +clear directions next to the object code saying where to find the +Corresponding Source. Regardless of what server hosts the +Corresponding Source, you remain obligated to ensure that it is +available for as long as needed to satisfy these requirements. + +e) Convey the object code using peer-to-peer transmission, provided +you inform other peers where the object code and Corresponding +Source of the work are being offered to the general public at no +charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + +a) Disclaiming warranty or limiting liability differently from the +terms of sections 15 and 16 of this License; or + +b) Requiring preservation of specified reasonable legal notices or +author attributions in that material or in the Appropriate Legal +Notices displayed by works containing it; or + +c) Prohibiting misrepresentation of the origin of that material, or +requiring that modified versions of such material be marked in +reasonable ways as different from the original version; or + +d) Limiting the use for publicity purposes of names of licensors or +authors of the material; or + +e) Declining to grant rights under trademark law for use of some +trade names, trademarks, or service marks; or + +f) Requiring indemnification of licensors and authors of that +material by anyone who conveys the material (or modified versions of +it) with contractual assumptions of liability to the recipient, for +any liability that these contractual assumptions directly impose on +those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + +8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + +13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + +MediaCMS: Modern, fully featured open source video and media CMS +Copyright (C) 2020 Markos Gogoulos and Yiannis Stergiou + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + +You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1286c24 --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +![MediaCMS](static/images/logo_dark.png) + +MediaCMS is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media. It can be used to build a small to medium video and media portal within minutes. + +It is built mostly using the modern stack Django + React and includes a REST API. + +## Screenshots + +![MediaCMS](docs/images/index.jpg) + +Vanilla MediaCMS index page + +![MediaCMS](docs/images/video.jpg) + +Video page with player different options + +![MediaCMS](docs/images/embed.jpg) + +Embed video page + + +## Features +- **Complete control over your data**: host it yourself! +- **Support for multiple publishing workflows**: public, private, unlisted and custom +- **Modern technologies**: Django/Python/Celery, React. +- **Multiple media types support**: video, audio, image, pdf +- **Multiple media classification options**: categories, tags and custom +- **Multiple media sharing options**: social media share, videos embed code generation +- **Easy media searching**: enriched with live search functionality +- **Responsive design**: including light and dark themes +- **Advanced users management**: allow self registration, invite only, closed. +- **Configurable actions**: allow download, add comments, add likes, dislikes, report media +- **Configuration options**: change logos, fonts, styling, add more pages +- **Enhanced video player**: customized video.js player with multiple resolution and playback speed options +- **Multiple transcoding profiles**: sane defaults for multiple dimensions (240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9) +- **Adaptive video streaming**: possible through HLS protocol +- **Subtitles/CC**: support for multilingual subtitle files +- **Scalable transcoding**: transcoding through priorities. Experimental support for remote workers +- **Chunked file uploads**: for pausable/resumable upload of content + + +## Example cases + +- **Schools, education.** Administrators and editors keep what content will be published, students are not distracted with advertisements and irrelevant content, plus they have the ability to select either to stream or download content. + +- **Organization sensitive content.** In cases where content is sensitive and cannot be uploaded to external sites. + +- **Build a great community.** MediaCMS can be customized (URLs, logos, fonts, aesthetics) so that you create a highly customized video portal for your community! + +- **Personal portal.** Organize, categorize and host your content the way you prefer. + + +## Philosophy + +We believe there's a need for quality open source web applications that can be used to build community portals and support collaboration. + +We have three goals for MediaCMS: a) deliver all functionality one would expect from a modern system, b) allow for easy installation and maintenance, c) allow easy customization and addition of features. + + +## License + +MediaCMS is released under [GNU Affero General Public License v3.0 license](LICENSE.txt). +Copyright Markos Gogoulos and Yiannis Stergiou + + +## Support and paid services + +We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Contact us at info@mediacms.io for more information. + + + +## Hardware dependencies + +For a small to medium installation, with a few hours of video uploaded daily, and a few hundreds of active daily users viewing content, 4GB Ram / 2-4 CPUs as minimum is ok. For a larger installation with many hours of video uploaded daily, consider adding more CPUs and more Ram. + +In terms of disk space, think of what the needs will be. A general rule is to multiply by three the size of the expected uploaded videos (since the system keeps original versions, encoded versions plus HLS), so if you receive 1G of videos daily and maintain all of them, you should consider a 1T disk across a year (1G * 3 * 365). + + +## Install + +The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu 18 or 20 versions. + +Installation on a Ubuntu 18 or 20 system with git utility installed should be completed in a few minutes with the following steps. +Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings. + +Automated script - to run on Ubuntu 18 or Ubuntu 20 flavors only! + +```bash +mkdir /home/mediacms.io && cd /home/mediacms.io/ +git clone https://github.com/mediacms-io/mediacms +cd /home/mediacms.io/mediacms/ && bash ./install.sh +``` + +The script will ask if you have a URL where you want to deploy MediaCMS, otherwise it will use localhost. If you provide a URL, it will use Let's Encrypt service to install a valid ssl certificate. + + +## Configure + +Several options are available on cms/settings.py, most of the things that are allowed or should be disallowed are described there. It is advisable to override any of them by adding it to cms/local_settings.py. All configuration options will be documented gradually on the [Configuration](docs/Configuration.md) page. + +## Authors +MediaCMS is developed by Yiannis Stergiou and Markos Gogoulos. We are Wordgames - https://wordgames.gr + + +## Technology +This software uses the following list of awesome technologies: +- Python +- Django +- Django Rest Framework +- Celery +- PostgreSQL +- Redis +- Nginx +- uWSGI +- React +- Fine Uploader +- video.js +- FFMPEG +- Bento4 + + +## Who is using it + +- **EngageMedia** non-profit media, technology and culture organization - https://video.engagemedia.org + +- **Critical Commons** public media archive and fair use advocacy network - https://criticalcommons.org + +- **Heritales** International Heritage Film Festival - https://stage.heritales.org + + +## Thanks To + +- **Anna Helme**, for such a great partnership all these years! + +- **Steve Anderson**, for trusting us and helping the Wordgames team make this real. + +- **Andrew Lowenthal, King Catoy, Rezwan Islam** and the rest of the great team of [Engage Media](https://engagemedia.org). + +- **Ioannis Korovesis, Ioannis Maistros, Diomidis Spinellis and Theodoros Karounos**, for their mentorship all these years, their contribution to science and the promotion of open source and free software technologies. + +- **Antonis Ikonomou**, for hosting us on the excellent [Innovathens](https://www.innovathens.gr) space. + +- **Werner Robitza**, for helping us with ffmpeg related stuff. + + +## Contact +info@mediacms.io diff --git a/actions/__init__.py b/actions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/actions/admin.py b/actions/admin.py new file mode 100644 index 0000000..e69de29 diff --git a/actions/apps.py b/actions/apps.py new file mode 100644 index 0000000..ce61600 --- /dev/null +++ b/actions/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class ActionsConfig(AppConfig): + name = "actions" diff --git a/actions/migrations/0001_initial.py b/actions/migrations/0001_initial.py new file mode 100644 index 0000000..4f6a65d --- /dev/null +++ b/actions/migrations/0001_initial.py @@ -0,0 +1,54 @@ +# Generated by Django 3.1.4 on 2020-12-01 07:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="MediaAction", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "session_key", + models.CharField( + blank=True, + db_index=True, + help_text="for not logged in users", + max_length=33, + null=True, + ), + ), + ( + "action", + models.CharField( + choices=[ + ("like", "Like"), + ("dislike", "Dislike"), + ("watch", "Watch"), + ("report", "Report"), + ("rate", "Rate"), + ], + default="watch", + max_length=20, + ), + ), + ("extra_info", models.TextField(blank=True, null=True)), + ("action_date", models.DateTimeField(auto_now_add=True)), + ("remote_ip", models.CharField(blank=True, max_length=40, null=True)), + ], + ), + ] diff --git a/actions/migrations/0002_mediaaction_media.py b/actions/migrations/0002_mediaaction_media.py new file mode 100644 index 0000000..0a0372a --- /dev/null +++ b/actions/migrations/0002_mediaaction_media.py @@ -0,0 +1,26 @@ +# Generated by Django 3.1.4 on 2020-12-01 07:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("actions", "0001_initial"), + ("files", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="mediaaction", + name="media", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="mediaactions", + to="files.media", + ), + ), + ] diff --git a/actions/migrations/0003_auto_20201201_0712.py b/actions/migrations/0003_auto_20201201_0712.py new file mode 100644 index 0000000..80178ef --- /dev/null +++ b/actions/migrations/0003_auto_20201201_0712.py @@ -0,0 +1,42 @@ +# Generated by Django 3.1.4 on 2020-12-01 07:12 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("actions", "0002_mediaaction_media"), + ] + + operations = [ + migrations.AddField( + model_name="mediaaction", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="useractions", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddIndex( + model_name="mediaaction", + index=models.Index( + fields=["user", "action", "-action_date"], + name="actions_med_user_id_940054_idx", + ), + ), + migrations.AddIndex( + model_name="mediaaction", + index=models.Index( + fields=["session_key", "action"], name="actions_med_session_fac55a_idx" + ), + ), + ] diff --git a/actions/migrations/__init__.py b/actions/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/actions/models.py b/actions/models.py new file mode 100644 index 0000000..64a6d4d --- /dev/null +++ b/actions/models.py @@ -0,0 +1,55 @@ +from django.db import models +from users.models import User +from files.models import Media + +USER_MEDIA_ACTIONS = ( + ("like", "Like"), + ("dislike", "Dislike"), + ("watch", "Watch"), + ("report", "Report"), + ("rate", "Rate"), +) + + +class MediaAction(models.Model): + """Stores different user actions""" + + user = models.ForeignKey( + User, + on_delete=models.CASCADE, + db_index=True, + blank=True, + null=True, + related_name="useractions", + ) + session_key = models.CharField( + max_length=33, + db_index=True, + blank=True, + null=True, + help_text="for not logged in users", + ) + + action = models.CharField( + max_length=20, choices=USER_MEDIA_ACTIONS, default="watch" + ) + # keeps extra info, eg on report action, why it is reported + extra_info = models.TextField(blank=True, null=True) + + media = models.ForeignKey( + Media, on_delete=models.CASCADE, related_name="mediaactions" + ) + action_date = models.DateTimeField(auto_now_add=True) + remote_ip = models.CharField(max_length=40, blank=True, null=True) + + def save(self, *args, **kwargs): + super(MediaAction, self).save(*args, **kwargs) + + def __str__(self): + return self.action + + class Meta: + indexes = [ + models.Index(fields=["user", "action", "-action_date"]), + models.Index(fields=["session_key", "action"]), + ] diff --git a/actions/tests.py b/actions/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/actions/views.py b/actions/views.py new file mode 100644 index 0000000..e69de29 diff --git a/cms/__init__.py b/cms/__init__.py new file mode 100644 index 0000000..1afbeb5 --- /dev/null +++ b/cms/__init__.py @@ -0,0 +1,4 @@ +from __future__ import absolute_import +from .celery import app as celery_app + +__all__ = ["celery_app"] diff --git a/cms/celery.py b/cms/celery.py new file mode 100644 index 0000000..8b21f5b --- /dev/null +++ b/cms/celery.py @@ -0,0 +1,16 @@ +from __future__ import absolute_import +import os +from celery import Celery + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cms.settings") +app = Celery("cms") + +app.config_from_object("django.conf:settings") +app.autodiscover_tasks() + +app.conf.beat_schedule = app.conf.CELERY_BEAT_SCHEDULE +app.conf.broker_transport_options = {"visibility_timeout": 60 * 60 * 24} # 1 day +# http://docs.celeryproject.org/en/latest/getting-started/brokers/redis.html#redis-caveats + + +app.conf.worker_prefetch_multiplier = 1 diff --git a/cms/custom_pagination.py b/cms/custom_pagination.py new file mode 100644 index 0000000..170ed61 --- /dev/null +++ b/cms/custom_pagination.py @@ -0,0 +1,29 @@ +from rest_framework.pagination import PageNumberPagination +from rest_framework.response import Response +from collections import OrderedDict # requires Python 2.7 or later +from django.core.paginator import Paginator +from django.utils.functional import cached_property + + +class FasterDjangoPaginator(Paginator): + @cached_property + def count(self): + return 50 + + +class FastPaginationWithoutCount(PageNumberPagination): + """Experimental, for cases where a SELECT COUNT is redundant""" + + django_paginator_class = FasterDjangoPaginator + + def get_paginated_response(self, data): + + return Response( + OrderedDict( + [ + ("next", self.get_next_link()), + ("previous", self.get_previous_link()), + ("results", data), + ] + ) + ) diff --git a/cms/permissions.py b/cms/permissions.py new file mode 100644 index 0000000..f456dbe --- /dev/null +++ b/cms/permissions.py @@ -0,0 +1,64 @@ +from django.conf import settings +from rest_framework import permissions +from files.methods import is_mediacms_editor, is_mediacms_manager + + +class IsAuthorizedToAdd(permissions.BasePermission): + def has_permission(self, request, view): + if request.method in permissions.SAFE_METHODS: + return True + return user_allowed_to_upload(request) + + +class IsUserOrManager(permissions.BasePermission): + """To be used in cases where request.user is either the + object owner, or anyone amongst MediaCMS managers + or superusers + """ + + def has_object_permission(self, request, view, obj): + if request.method in permissions.SAFE_METHODS: + return True + if request.user.is_superuser: + return True + if is_mediacms_manager(request.user): + return True + + return obj == request.user + + +class IsUserOrEditor(permissions.BasePermission): + """To be used in cases where request.user is either the + object owner, or anyone amongst MediaCMS editors, managers + or superusers + """ + + def has_object_permission(self, request, view, obj): + if request.method in permissions.SAFE_METHODS: + return True + if request.user.is_superuser: + return True + if is_mediacms_editor(request.user): + return True + + return obj == request.user + + +def user_allowed_to_upload(request): + """Any custom logic for whether a user is allowed + to upload content lives here + """ + if request.user.is_anonymous: + return False + if request.user.is_superuser: + return True + + if settings.CAN_ADD_MEDIA == "all": + return True + elif settings.CAN_ADD_MEDIA == "email_verified": + if request.user.email_is_verified: + return True + elif settings.CAN_ADD_MEDIA == "advancedUser": + if request.user.advancedUser: + return True + return False diff --git a/cms/settings.py b/cms/settings.py new file mode 100644 index 0000000..b5de473 --- /dev/null +++ b/cms/settings.py @@ -0,0 +1,437 @@ +import os +from celery.schedules import crontab + +DEBUG = False + +# PORTAL NAME, this is the portal title and +# is also shown on several places as emails +PORTAL_NAME = "MediaCMS" +LANGUAGE_CODE = "en-us" +TIME_ZONE = "Europe/London" + +# who can add media +# valid options include 'all', 'email_verified', 'advancedUser' +CAN_ADD_MEDIA = "all" + +# valid choices here are 'public', 'private', 'unlisted +PORTAL_WORKFLOW = "public" + +DEFAULT_THEME = "black" # this is not taken under consideration currently + + +# These are passed on every request +# if set to False will not fetch external content +# this is only for the static files, as fonts/css/js files loaded from CDNs +# not for user uploaded media! +LOAD_FROM_CDN = True +LOGIN_ALLOWED = True # whether the login button appears +REGISTER_ALLOWED = True # whether the register button appears +UPLOAD_MEDIA_ALLOWED = True # whether the upload media button appears +CAN_LIKE_MEDIA = True # whether the like media appears +CAN_DISLIKE_MEDIA = True # whether the dislike media appears +CAN_REPORT_MEDIA = True # whether the report media appears +CAN_SHARE_MEDIA = True # whether the share media appears +# how many times an item need be reported +# to get to private state automatically +REPORTED_TIMES_THRESHOLD = 10 +ALLOW_ANONYMOUS_ACTIONS = ["report", "like", "dislike", "watch"] # need be a list + +# experimental functionality for user ratings - does not work +ALLOW_RATINGS = False +ALLOW_RATINGS_CONFIRMED_EMAIL_ONLY = True + +# ip of the server should be part of this +ALLOWED_HOSTS = ["*", "mediacms.io", "127.0.0.1", "localhost"] +FRONTEND_HOST = "http://localhost" +# FRONTEND_HOST needs an http prefix - at the end of the file +# there's a conversion to https with the SSL_FRONTEND_HOST env +INTERNAL_IPS = "127.0.0.1" + +# settings that are related with UX/appearance +# whether a featured item appears enlarged with player on index page +VIDEO_PLAYER_FEATURED_VIDEO_ON_INDEX_PAGE = False + +PRE_UPLOAD_MEDIA_MESSAGE = "" + +# email settings +DEFAULT_FROM_EMAIL = "info@mediacms.io" +EMAIL_HOST_PASSWORD = "xyz" +EMAIL_HOST_USER = "info@mediacms.io" +EMAIL_USE_TLS = True +SERVER_EMAIL = DEFAULT_FROM_EMAIL +EMAIL_HOST = "mediacms.io" +EMAIL_PORT = 587 +ADMIN_EMAIL_LIST = ["info@mediacms.io"] + + +MEDIA_IS_REVIEWED = True # whether an admin needs to review a media file. +# By default consider this is not needed. +# If set to False, then each new media need be reviewed otherwise +# it won't appear on public listings + +# if set to True the url for original file is returned to the API. +SHOW_ORIGINAL_MEDIA = True +# Keep in mind that nginx will serve the file unless there's +# some authentication taking place. Check nginx file and setup a +# basic http auth user/password if you want to restrict access + +MAX_MEDIA_PER_PLAYLIST = 70 +# bytes, size of uploaded media +UPLOAD_MAX_SIZE = 800 * 1024 * 1000 * 5 + +MAX_CHARS_FOR_COMMENT = 10000 # so that it doesn't end up huge + +# valid options: content, author +RELATED_MEDIA_STRATEGY = "content" + +USE_I18N = True +USE_L10N = True +USE_TZ = True +SITE_ID = 1 + +# protection agains anonymous users +# per ip address limit, for actions as like/dislike/report +TIME_TO_ACTION_ANONYMOUS = 10 * 60 + +# django-allauth settings +ACCOUNT_SESSION_REMEMBER = True +ACCOUNT_AUTHENTICATION_METHOD = "username_email" +ACCOUNT_EMAIL_REQUIRED = True # new users need to specify email +ACCOUNT_EMAIL_VERIFICATION = "optional" # 'mandatory' 'none' +ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True +ACCOUNT_USERNAME_MIN_LENGTH = "4" +ACCOUNT_ADAPTER = "users.adapter.MyAccountAdapter" +ACCOUNT_SIGNUP_FORM_CLASS = "users.forms.SignupForm" +ACCOUNT_USERNAME_VALIDATORS = "users.validators.custom_username_validators" +ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False +ACCOUNT_USERNAME_REQUIRED = True +ACCOUNT_LOGIN_ON_PASSWORD_RESET = True +ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1 +ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 20 +ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 5 +# registration won't be open, might also consider to remove links for register +USERS_CAN_SELF_REGISTER = True + +RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = ["xxx.com", "emaildomainwhatever.com"] + +# django rest settings +REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework.authentication.SessionAuthentication", + "rest_framework.authentication.BasicAuthentication", + "rest_framework.authentication.TokenAuthentication", + ), + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 50, + "DEFAULT_PARSER_CLASSES": [ + "rest_framework.parsers.JSONParser", + ], +} + + +SECRET_KEY = "2dii4cog7k=5n37$fz)8dst)kg(s3&10)^qa*gv(kk+nv-z&cu" +# TODO: this needs to be changed! + +TEMP_DIRECTORY = "/tmp" # Don't use a temp directory inside BASE_DIR!!! +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +STATIC_URL = "/static/" # where js/css files are stored on the filesystem +MEDIA_URL = "/media/" # URL where static files are served from the server +STATIC_ROOT = BASE_DIR + "/static/" +# where uploaded + encoded media are stored +MEDIA_ROOT = BASE_DIR + "/media_files/" + +MEDIA_UPLOAD_DIR = os.path.join(MEDIA_ROOT, "original/") +MEDIA_ENCODING_DIR = os.path.join(MEDIA_ROOT, "encoded/") +THUMBNAIL_UPLOAD_DIR = os.path.join(MEDIA_UPLOAD_DIR, "thumbnails/") +SUBTITLES_UPLOAD_DIR = os.path.join(MEDIA_UPLOAD_DIR, "subtitles/") +HLS_DIR = os.path.join(MEDIA_ROOT, "hls/") + +FFMPEG_COMMAND = "ffmpeg" # this is the path +FFPROBE_COMMAND = "ffprobe" # this is the path +MP4HLS = "mp4hls" + +MASK_IPS_FOR_ACTIONS = True +# how many seconds a process in running state without reporting progress is +# considered as stale...unfortunately v9 seems to not include time +# some times so raising this high +RUNNING_STATE_STALE = 60 * 60 * 2 + +FRIENDLY_TOKEN_LEN = 9 + +# for videos, after that duration get split into chunks +# and encoded independently +CHUNKIZE_VIDEO_DURATION = 60 * 5 +# aparently this has to be smaller than VIDEO_CHUNKIZE_DURATION +VIDEO_CHUNKS_DURATION = 60 * 4 + +# always get these two, even if upscaling +MINIMUM_RESOLUTIONS_TO_ENCODE = [240, 360] + +# default settings for notifications +# not all of them are implemented + +USERS_NOTIFICATIONS = { + "MEDIA_ADDED": True, # in use + "MEDIA_ENCODED": False, # not implemented + "MEDIA_REPORTED": False, # not implemented +} + +ADMINS_NOTIFICATIONS = { + "NEW_USER": True, # in use + "MEDIA_ADDED": True, # in use + "MEDIA_ENCODED": False, # not implemented + "MEDIA_REPORTED": True, # in use +} + + +# this is for fineuploader - media uploads +UPLOAD_DIR = "uploads/" +CHUNKS_DIR = "chunks/" + +# number of files to upload using fineuploader at once +UPLOAD_MAX_FILES_NUMBER = 100 +CONCURRENT_UPLOADS = True +CHUNKS_DONE_PARAM_NAME = "done" +FILE_STORAGE = "django.core.files.storage.DefaultStorage" + +X_FRAME_OPTIONS = "ALLOWALL" +EMAIL_BACKEND = "djcelery_email.backends.CeleryEmailBackend" +CELERY_EMAIL_TASK_CONFIG = { + "queue": "short_tasks", +} + +POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY = "" +# a message to be shown on the author of a media file and only +# only in case where unlisted workflow is used and no commentary +# exists + +CANNOT_ADD_MEDIA_MESSAGE = "" + +# mp4hls command, part of Bendo4 +MP4HLS_COMMAND = ( + "/home/mediacms.io/mediacms/Bento4-SDK-1-6-0-632.x86_64-unknown-linux/bin/mp4hls" +) + +# highly experimental, related with remote workers +ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24" +# this is used by remote workers to push +# encodings once they are done +# USE_BASIC_HTTP = True +# BASIC_HTTP_USER_PAIR = ('user', 'password') +# specify basic auth user/password pair for use with the +# remote workers, if nginx basic auth is setup +# apache2-utils need be installed +# then run +# htpasswd -c /home/mediacms.io/mediacms/deploy/.htpasswd user +# and set a password +# edit /etc/nginx/sites-enabled/mediacms.io and +# uncomment the two lines related to htpasswd + + +CKEDITOR_CONFIGS = { + "default": { + "toolbar": "Custom", + "width": "100%", + "toolbar_Custom": [ + ["Styles"], + ["Format"], + ["Bold", "Italic", "Underline"], + ["HorizontalRule"], + [ + "NumberedList", + "BulletedList", + "-", + "Outdent", + "Indent", + "-", + "JustifyLeft", + "JustifyCenter", + "JustifyRight", + "JustifyBlock", + ], + ["Link", "Unlink"], + ["Image"], + ["RemoveFormat", "Source"], + ], + } +} + + +AUTH_USER_MODEL = "users.User" +LOGIN_REDIRECT_URL = "/" + +AUTHENTICATION_BACKENDS = ( + "django.contrib.auth.backends.ModelBackend", + "allauth.account.auth_backends.AuthenticationBackend", +) + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "allauth", + "allauth.account", + "allauth.socialaccount", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", + "rest_framework", + "rest_framework.authtoken", + "imagekit", + "files.apps.FilesConfig", + "users.apps.UsersConfig", + "actions.apps.ActionsConfig", + "debug_toolbar", + "mptt", + "crispy_forms", + "uploader.apps.UploaderConfig", + "djcelery_email", + "ckeditor", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "debug_toolbar.middleware.DebugToolbarMiddleware", +] + +ROOT_URLCONF = "cms.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": ["templates"], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.media", + "django.contrib.messages.context_processors.messages", + "files.context_processors.stuff", + ], + }, + }, +] + +WSGI_APPLICATION = "cms.wsgi.application" + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "OPTIONS": { + "min_length": 5, + }, + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, +] + +FILE_UPLOAD_HANDLERS = [ + "django.core.files.uploadhandler.TemporaryFileUploadHandler", +] + +LOGS_DIR = os.path.join(BASE_DIR, "logs") + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "file": { + "level": "ERROR", + "class": "logging.FileHandler", + "filename": os.path.join(LOGS_DIR, "debug.log"), + }, + }, + "loggers": { + "django": { + "handlers": ["file"], + "level": "ERROR", + "propagate": True, + }, + }, +} + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": "mediacms", + "HOST": "127.0.0.1", + "PORT": "5432", + "USER": "mediacms", + "PASSWORD": "mediacms", + } +} + + +REDIS_LOCATION = "redis://127.0.0.1:6379/1" +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": REDIS_LOCATION, + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + } +} + +SESSION_ENGINE = "django.contrib.sessions.backends.cache" +SESSION_CACHE_ALIAS = "default" + +# CELERY STUFF +BROKER_URL = REDIS_LOCATION +CELERY_RESULT_BACKEND = BROKER_URL +CELERY_ACCEPT_CONTENT = ["application/json"] +CELERY_TASK_SERIALIZER = "json" +CELERY_RESULT_SERIALIZER = "json" +CELERY_TIMEZONE = TIME_ZONE +CELERY_SOFT_TIME_LIMIT = 2 * 60 * 60 +CELERY_WORKER_PREFETCH_MULTIPLIER = 1 +CELERYD_PREFETCH_MULTIPLIER = 1 + +CELERY_BEAT_SCHEDULE = { + # clear expired sessions, every sunday 1.01am. By default Django has 2week + # expire date + "clear_sessions": { + "task": "clear_sessions", + "schedule": crontab(hour=1, minute=1, day_of_week=6), + }, + "get_list_of_popular_media": { + "task": "get_list_of_popular_media", + "schedule": crontab(minute=1, hour="*/10"), + }, + "update_listings_thumbnails": { + "task": "update_listings_thumbnails", + "schedule": crontab(minute=2, hour="*/30"), + }, +} +# TODO: beat, delete chunks from media root +# chunks_dir after xx days...(also uploads_dir) + +try: + # keep a local_settings.py file for local overrides + from .local_settings import * + + # ALLOWED_HOSTS needs a url/ip + ALLOWED_HOSTS.append(FRONTEND_HOST.replace("http://", "").replace("https://", "")) +except ImportError: + # local_settings not in use + pass + + +if "http" not in FRONTEND_HOST: + # FRONTEND_HOST needs a http:// preffix + FRONTEND_HOST = f"http://{FRONTEND_HOST}" + +SSL_FRONTEND_HOST = FRONTEND_HOST.replace("http", "https") diff --git a/cms/urls.py b/cms/urls.py new file mode 100644 index 0000000..ce6692a --- /dev/null +++ b/cms/urls.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from django.urls import path +from django.conf.urls import url, include +import debug_toolbar + +urlpatterns = [ + url(r"^__debug__/", include(debug_toolbar.urls)), + url(r"^", include("files.urls")), + url(r"^", include("users.urls")), + url(r"^accounts/", include("allauth.urls")), + url(r"^api-auth/", include("rest_framework.urls")), + path("admin/", admin.site.urls), +] diff --git a/cms/wsgi.py b/cms/wsgi.py new file mode 100644 index 0000000..5f714e4 --- /dev/null +++ b/cms/wsgi.py @@ -0,0 +1,7 @@ +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cms.settings") + +application = get_wsgi_application() diff --git a/deploy/celery_beat.service b/deploy/celery_beat.service new file mode 100644 index 0000000..63a1333 --- /dev/null +++ b/deploy/celery_beat.service @@ -0,0 +1,24 @@ +[Unit] +Description=MediaCMS celery beat +After=network.target + +[Service] +Type=simple +User=www-data +Group=www-data +Restart=always +RestartSec=10 +Environment=APP_DIR="/home/mediacms.io/mediacms" +Environment=CELERY_BIN="/home/mediacms.io/bin/celery" +Environment=CELERY_APP="cms" +Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/beat%n.pid" +Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/beat%N.log" +Environment=CELERYD_LOG_LEVEL="INFO" +Environment=APP_DIR="/home/mediacms.io/mediacms" + +ExecStart=/bin/sh -c '${CELERY_BIN} beat -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR}' +ExecStop=/bin/kill -s TERM $MAINPID + +[Install] +WantedBy=multi-user.target + diff --git a/deploy/celery_long.service b/deploy/celery_long.service new file mode 100644 index 0000000..ab8ab8a --- /dev/null +++ b/deploy/celery_long.service @@ -0,0 +1,31 @@ +[Unit] +Description=MediaCMS celery long queue +After=network.target + +[Service] +Type=forking +User=www-data +Group=www-data +Restart=always +RestartSec=10 +Environment=APP_DIR="/home/mediacms.io/mediacms" +Environment=CELERYD_NODES="long1" +Environment=CELERY_QUEUE="long_tasks" +Environment=CELERY_BIN="/home/mediacms.io/bin/celery" +Environment=CELERY_APP="cms" +Environment=CELERYD_MULTI="multi" +Environment=CELERYD_OPTS="-Ofair --prefetch-multiplier=1" +Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid" +Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log" +Environment=CELERYD_LOG_LEVEL="INFO" +Environment=APP_DIR="/home/mediacms.io/mediacms" + +ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}' + +ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}' + +ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}' + +[Install] +WantedBy=multi-user.target + diff --git a/deploy/celery_short.service b/deploy/celery_short.service new file mode 100644 index 0000000..0ac108b --- /dev/null +++ b/deploy/celery_short.service @@ -0,0 +1,41 @@ +[Unit] +Description=MediaCMS celery short queue +After=network.target + +[Service] +Type=forking +User=www-data +Group=www-data +Restart=always +RestartSec=10 +Environment=APP_DIR="/home/mediacms.io/mediacms" +Environment=CELERYD_NODES="short1 short2" +Environment=CELERY_QUEUE="short_tasks" +# Absolute or relative path to the 'celery' command: +Environment=CELERY_BIN="/home/mediacms.io/bin/celery" +# App instance to use +# comment out this line if you don't use an app +Environment=CELERY_APP="cms" +# or fully qualified: +#CELERY_APP="proj.tasks:app" +# How to call manage.py +Environment=CELERYD_MULTI="multi" +# Extra command-line arguments to the worker +Environment=CELERYD_OPTS="--soft-time-limit=300 -c10" +# - %n will be replaced with the first part of the nodename. +# - %I will be replaced with the current child process index +# and is important when using the prefork pool to avoid race conditions. +Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid" +Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log" +Environment=CELERYD_LOG_LEVEL="INFO" +Environment=APP_DIR="/home/mediacms.io/mediacms" + +ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}' + +ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}' + +ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}' + +[Install] +WantedBy=multi-user.target + diff --git a/deploy/mediacms.io b/deploy/mediacms.io new file mode 100644 index 0000000..0a0b279 --- /dev/null +++ b/deploy/mediacms.io @@ -0,0 +1,78 @@ +server { + listen 80 ; + server_name localhost; + + gzip on; + access_log /var/log/nginx/mediacms.io.access.log; + + error_log /var/log/nginx/mediacms.io.error.log warn; + + # redirect to https if logged in + if ($http_cookie ~* "sessionid") { + rewrite ^/(.*)$ https://localhost/$1 permanent; + } + + # redirect basic forms to https + location ~ (login|login_form|register|mail_password_form)$ { + rewrite ^/(.*)$ https://localhost/$1 permanent; + } + + location /static { + alias /home/mediacms.io/mediacms/static ; + } + + location /media/original { + alias /home/mediacms.io/mediacms/media_files/original; + } + + location /media { + alias /home/mediacms.io/mediacms/media_files ; + } + + location / { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; + + include /etc/nginx/sites-enabled/uwsgi_params; + uwsgi_pass 127.0.0.1:9000; + } +} + +server { + listen 443 ssl; + server_name localhost; + + ssl_certificate_key /etc/letsencrypt/live/localhost/privkey.pem; + ssl_certificate /etc/letsencrypt/live/localhost/fullchain.pem; + + gzip on; + access_log /var/log/nginx/mediacms.io.access.log; + + error_log /var/log/nginx/mediacms.io.error.log warn; + + location /static { + alias /home/mediacms.io/mediacms/static ; + } + + location /media/original { + alias /home/mediacms.io/mediacms/media_files/original; + #auth_basic "auth protected area"; + #auth_basic_user_file /home/mediacms.io/mediacms/deploy/.htpasswd; + } + + location /media { + alias /home/mediacms.io/mediacms/media_files ; + } + + location / { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; + + include /etc/nginx/sites-enabled/uwsgi_params; + uwsgi_pass 127.0.0.1:9000; + } +} diff --git a/deploy/mediacms.io_fullchain.pem b/deploy/mediacms.io_fullchain.pem new file mode 100644 index 0000000..551ca6c --- /dev/null +++ b/deploy/mediacms.io_fullchain.pem @@ -0,0 +1,58 @@ +-----BEGIN CERTIFICATE----- +MIIFTjCCBDagAwIBAgISBNOUeDlerH9MkKmHLvZJeMYgMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDAzMTAxNzUxNDFaFw0y +MDA2MDgxNzUxNDFaMBYxFDASBgNVBAMTC21lZGlhY21zLmlvMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAps5Jn18nW2tq/LYFDgQ1YZGLlpF/B2AAPvvH +3yuD+AcT4skKdZouVL/a5pXrptuYL5lthO9dlcja2tuO2ltYrb7Dp01dAIFaJE8O +DKd+Sv5wr8VWQZykqzMiMBgviml7TBvUHQjvCJg8UwmnN0XSUILCttd6u4qOzS7d +lKMMsKpYzLhElBT0rzhhsWulDiy6aAZbMV95bfR74nIWsBJacy6jx3jvxAuvCtkB +OVdOoVL6BPjDE3SNEk53bAZGIb5A9ri0O5jh/zBFT6tQSjUhAUTkmv9oZP547RnV +fDj+rdvCVk/fE+Jno36mcT183Qd/Ty3fWuqFoM5g/luhnfvWEwIDAQABo4ICYDCC +AlwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTd5EZBt74zu5XxT1uXQs6oM8qOuDAf +BgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEw +LgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcw +LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv +MBYGA1UdEQQPMA2CC21lZGlhY21zLmlvMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcG +CysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5 +cHQub3JnMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAXqdz+d9WwOe1Nkh90Eng +MnqRmgyEoRIShBh1loFxRVgAAAFwxcnL+AAABAMARzBFAiAb3yeBuW3j9MxcRc0T +icUBvEa/rH7Fv2eB0oQlnZ1exQIhAPf+CtTXmzxoeT/BBiivj4AmGDsq4xWhe/U6 +BytYrKLeAHYAB7dcG+V9aP/xsMYdIxXHuuZXfFeUt2ruvGE6GmnTohwAAAFwxcnM +HAAABAMARzBFAiAuP5gKyyaT0LVXxwjYD9zhezvxf4Icx0P9pk75c5ao+AIhAK0+ +fSJv+WTXciMT6gA1sk/tuCHuDFAuexSA/6TcRXcVMA0GCSqGSIb3DQEBCwUAA4IB +AQCPCYBU4Q/ro2MUkjDPKGmeqdxQycS4R9WvKTG/nmoahKNg30bnLaDPUcpyMU2k +sPDemdZ7uTGLZ3ZrlIva8DbrnJmrTPf9BMwaM6j+ZV/QhxvKZVIWkLkZrwiVI57X +Ba+rs5IEB4oWJ0EBaeIrzeKG5zLMkRcIdE4Hlhuwu3zGG56c+wmAPuvpIDlYoO6o +W22xRdxoTIHBvkzwonpVYUaRcaIw+48xnllxh1dHO+X69DT45wlF4tKveOUi+L50 +4GWJ8Vjv7Fot/WNHEM4Mnmw0jHj9TPkIZKnPNRMdHmJ5CF/FJFDiptOeuzbfohG+ +mdvuInb8JDc0XBE99Gf/S4/y +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- diff --git a/deploy/mediacms.io_privkey.pem b/deploy/mediacms.io_privkey.pem new file mode 100644 index 0000000..d366f09 --- /dev/null +++ b/deploy/mediacms.io_privkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCmzkmfXydba2r8 +tgUOBDVhkYuWkX8HYAA++8ffK4P4BxPiyQp1mi5Uv9rmleum25gvmW2E712VyNra +247aW1itvsOnTV0AgVokTw4Mp35K/nCvxVZBnKSrMyIwGC+KaXtMG9QdCO8ImDxT +Cac3RdJQgsK213q7io7NLt2UowywqljMuESUFPSvOGGxa6UOLLpoBlsxX3lt9Hvi +chawElpzLqPHeO/EC68K2QE5V06hUvoE+MMTdI0STndsBkYhvkD2uLQ7mOH/MEVP +q1BKNSEBROSa/2hk/njtGdV8OP6t28JWT98T4mejfqZxPXzdB39PLd9a6oWgzmD+ +W6Gd+9YTAgMBAAECggEADnEJuryYQbf5GUwBAAepP3tEZJLQNqk/HDTcRxwTXuPt ++tKBD1F79WZu40vTjSyx7l0QOFQo/BDZsd0Ubx89fD1p3xA5nxOT5FTb2IifzIpe +4zjokOGo+BGDQjq10vvy6tH1+VWOrGXRwzawvX5UCRhpFz9sptQGLQmDsZy0Oo9B +LtavYVUqsbyqRWlzaclHgbythegIACWkqcalOzOtx+l6TGBRjej+c7URcwYBfr7t +XTAzbP+vnpaJovZyZT1eekr0OLzMpnjx4HvRvzL+NxauRpn6KfabsTfZlk8nrs4I +UdSjeukj1Iz8rGQilHdN/4dVJ3KzrlHVkVTBSjmMUQKBgQDaVXZnhAScfdiKeZbO +rdUAWcnwfkDghtRuAmzHaRM/FhFBEoVhdSbBuu+OUyBnIw/Ra4o2ePuEBcKIUiQO +w2tnE1CY5PPAcjw+OCSpvzy5xxjaqaRbm9BJp3FTeEYGLXERnchPpHg/NpexuF22 +QOJ+FrysPyNMxuQp47ZwO9WT3QKBgQDDlSGjq/eeWxemwf7ZqMVlRyqsdJsgnCew +DkC62IGiYCBDfeEmndN+vcA/uzJHYV4iXiqS3aYJCWGaZFMhdIhIn5MgULvO1j5G +u/MxuzaaNPz22FlNCWTLBw4T1HOOvyTL+nLtZDKJ/BHxgHCmur1kiGvvZWrcCthD +afLEmseqrwKBgBuLZKCymxJTHhp6NHhmndSpfzyD8RNibzJhw+90ZiUzV4HqIEGn +Ufhm6Qn/mrroRXqaIpm0saZ6Q4yHMF1cchRS73wahlXlE4yV8KopojOd1pjfhgi4 +o5JnOXjaV5s36GfcjATgLvtqm8CkDc6MaQaXP75LSNzKysYuIDoQkmVRAoGAAghF +rja2Pv4BU+lGJarcSj4gEmSvy/nza5/qSka/qhlHnIvtUAJp1TJRkhf24MkBOmgy +Fw6YkBV53ynVt05HsEGAPOC54t9VDFUdpNGmMpoEWuhKnUNQuc9b9RbLEJup3TjA +Avl8kPR+lzzXbtQX7biBLp6mKp0uPB0YubRGCN8CgYA0JMxK0x38Q2x3AQVhOmZh +YubtIa0JqVJhvpweOCFnkq3ebBpLsWYwiLTn86vuD0jupe5M3sxtefjkJmAKd8xY +aBU7QWhjh1fX4mzmggnbjcrIFbkIHsxwMeg567U/4AGxOOUsv9QUn37mqycqRKEn +YfUyYNLM6F3MmQAOs2kaHw== +-----END PRIVATE KEY----- diff --git a/deploy/mediacms.service b/deploy/mediacms.service new file mode 100644 index 0000000..4d81e53 --- /dev/null +++ b/deploy/mediacms.service @@ -0,0 +1,13 @@ +[Unit] +Description=MediaCMS uwsgi + +[Service] +ExecStart=/home/mediacms.io/bin/uwsgi --ini /home/mediacms.io/mediacms/uwsgi.ini +ExecStop=/usr/bin/killall -9 uwsgi +RestartSec=3 +#ExecRestart=killall -9 uwsgi; sleep 5; /home/sss/bin/uwsgi --ini /home/sss/wordgames/uwsgi.ini +Restart=always + + +[Install] +WantedBy=multi-user.target diff --git a/deploy/mediacms_logrorate b/deploy/mediacms_logrorate new file mode 100644 index 0000000..c4fc3c6 --- /dev/null +++ b/deploy/mediacms_logrorate @@ -0,0 +1,7 @@ +/home/mediacms.io/mediacms/logs/*.log { + weekly + missingok + rotate 7 + compress + notifempty +} diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 0000000..1dda610 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,41 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; + +events { + worker_connections 10240; +} + + worker_rlimit_nofile 20000; #each connection needs a filehandle (or 2 if you are proxying) +http { + proxy_connect_timeout 75; + proxy_read_timeout 12000; + client_max_body_size 5800M; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 10; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE + ssl_prefer_server_ciphers on; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + gzip on; + gzip_disable "msie6"; + + log_format compression '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" "$gzip_ratio"'; + + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + include /etc/nginx/conf.d/*.conf; + include /etc/nginx/sites-enabled/*; +} + diff --git a/deploy/uwsgi.ini b/deploy/uwsgi.ini new file mode 100644 index 0000000..b38fe84 --- /dev/null +++ b/deploy/uwsgi.ini @@ -0,0 +1,19 @@ +[uwsgi] + +chdir = /home/mediacms.io/mediacms/ +virtualenv = /home/mediacms.io +module = cms.wsgi + +uid = www-data +gid = www-data + +processes = 10 +threads = 10 +master = true +workers = 8 +vacuum = true + +socket = 127.0.0.1:9000 + +logto = /home/mediacms.io/mediacms/logs/errorlog.txt + diff --git a/deploy/uwsgi_params b/deploy/uwsgi_params new file mode 100644 index 0000000..5abf809 --- /dev/null +++ b/deploy/uwsgi_params @@ -0,0 +1,16 @@ +uwsgi_param QUERY_STRING $query_string; +uwsgi_param REQUEST_METHOD $request_method; +uwsgi_param CONTENT_TYPE $content_type; +uwsgi_param CONTENT_LENGTH $content_length; + +uwsgi_param REQUEST_URI $request_uri; +uwsgi_param PATH_INFO $document_uri; +uwsgi_param DOCUMENT_ROOT $document_root; +uwsgi_param SERVER_PROTOCOL $server_protocol; +uwsgi_param REQUEST_SCHEME $scheme; +uwsgi_param HTTPS $https if_not_empty; + +uwsgi_param REMOTE_ADDR $remote_addr; +uwsgi_param REMOTE_PORT $remote_port; +uwsgi_param SERVER_PORT $server_port; +uwsgi_param SERVER_NAME $server_name; diff --git a/docs/Configuration.md b/docs/Configuration.md new file mode 100644 index 0000000..78dab51 --- /dev/null +++ b/docs/Configuration.md @@ -0,0 +1,244 @@ +## Configuration + +A number of options are available on `cms/settings.py`. + +It is advisable to override any of them by adding it to `cms/local_settings.py` . + +Any change needs restart of MediaCMS in order to take effect. So edit `cms/local_settings.py`, make a change and restart MediaCMS + +``` +#systectl restart mediacms +``` + + +### change portal logo + +Set a new svg file for the white theme (`static/images/logo_dark.svg`) or the dark theme (`static/images/logo_light.svg`) + +### set global portal title + +set `PORTAL_NAME`, eg + +``` +PORTAL_NAME = 'my awesome portal' +``` + +### who can add media + +By default `CAN_ADD_MEDIA = "all"` means that all registered users can add media. Other valid options are: + +- **email_verified**, a user not only has to register an account but also verify the email (by clicking the link sent upon registration). Apparently email configuration need to work, otherise users won't receive emails. + +- **advancedUser**, only users that are marked as advanced users can add media. Admins or MediaCMS managers can make users advanced users by editing their profile and selecting advancedUser. + +### what is the portal workflow + +The `PORTAL_WORKFLOW` variable specifies what happens to newly uploaded media, whether they appear on listings (as the index page, or search) + +- **public** is the default option and means that a media can appear on listings. If media type is video, it will appear once at least a task that produces an encoded version of the file has finished succesfully. For other type of files, as image/audio they appear instantly + +- **private** means that newly uploaded content is private - only users can see it or MediaCMS editors, managers and admins. Those can also set the status to public or unlisted + +- **unlisted** means that items are unlisted. However if a user visits the url of an unlisted media, it will be shown (as opposed to private) + + +### show/hide the Sign in button + +to show button: +``` +LOGIN_ALLOWED = True +``` + +to hide button: + +``` +LOGIN_ALLOWED = False +``` + +### show/hide the Register button + +to show button: +``` +REGISTER_ALLOWED = True +``` + +to hide button: + +``` +REGISTER_ALLOWED = False +``` + + +### show/hide the upload media button + +To show: + +``` +UPLOAD_MEDIA_ALLOWED = True +``` + +To hide: + +``` +UPLOAD_MEDIA_ALLOWED = False +``` + +### show/hide the actions buttons (like/dislike/report) + +Make changes (True/False) to any of the following: + +``` +- CAN_LIKE_MEDIA = True # whether the like media appears +- CAN_DISLIKE_MEDIA = True # whether the dislike media appears +- CAN_REPORT_MEDIA = True # whether the report media appears +- CAN_SHARE_MEDIA = True # whether the share media appears +``` + +### automatically hide media upon being reported + +set a low number for variable `REPORTED_TIMES_THRESHOLD` +eg + +``` +REPORTED_TIMES_THRESHOLD = 2 +``` + +once the limit is reached, media goes to private state and an email is sent to admins + +### set a custom message on the media upload page + +this message will appear below the media drag and drop form + +``` +PRE_UPLOAD_MEDIA_MESSAGE = 'custom message' +``` + +### set email settings + +Set correct settings per provider + +``` +DEFAULT_FROM_EMAIL = 'info@mediacms.io' +EMAIL_HOST_PASSWORD = 'xyz' +EMAIL_HOST_USER = 'info@mediacms.io' +EMAIL_USE_TLS = True +SERVER_EMAIL = DEFAULT_FROM_EMAIL +EMAIL_HOST = 'mediacms.io' +EMAIL_PORT = 587 +ADMIN_EMAIL_LIST = ['info@mediacms.io'] +``` + +### disallow user registrations from specific domains + +set domains that are not valid for registration via this variable: + +``` +RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = [ + 'xxx.com', 'emaildomainwhatever.com'] +``` + +### require a review by MediaCMS editors/managers/admins + +set value + +``` +MEDIA_IS_REVIEWED = False +``` + +any uploaded media now needs to be reviewed before it can appear to the listings. +MediaCMS editors/managers/admins can visit the media page and edit it, where they can see the option to mark media as reviewed. By default this is set to True, so all media don't require to be reviewed + +### specify maximum number of media for a playlist + +set a different threshold on variable `MAX_MEDIA_PER_PLAYLIST` + +eg + +``` +MAX_MEDIA_PER_PLAYLIST = 14 +``` + +### specify maximum size of a media that can be uploaded + +change `UPLOAD_MAX_SIZE`. + +default is 4GB + +``` +UPLOAD_MAX_SIZE = 800 * 1024 * 1000 * 5 +``` + +### specify maximum size of comments + +change `MAX_CHARS_FOR_COMMENT` + +default: + +``` +MAX_CHARS_FOR_COMMENT = 10000 +``` + +### how many files to upload in parallel + +set a different threshold for `UPLOAD_MAX_FILES_NUMBER` +default: + +``` +UPLOAD_MAX_FILES_NUMBER = 100 +``` + +### force users confirm their email upon registrations + +default option for email confirmation is optional. Set this to mandatory in order to force users confirm their email before they can login + +``` +ACCOUNT_EMAIL_VERIFICATION = 'optional' +``` + +### rate limit account login attempts + +after this number is reached + +``` +ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 20 +``` + +sets a timeout (in seconds) + +``` +ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 5 +``` + +### disallow user registration + +set the following variable to False + +``` +USERS_CAN_SELF_REGISTER = True +``` + +### configure notifications + +Global notifications that are implemented are controlled by the following options: + +``` +USERS_NOTIFICATIONS = { + 'MEDIA_ADDED': True, +} +``` + +If you want to disable notification for new media, set to False + +Admins also receive notifications on different events, set any of the following to False to disable + +``` +ADMINS_NOTIFICATIONS = { + 'NEW_USER': True, + 'MEDIA_ADDED': True, + 'MEDIA_REPORTED': True, +} +``` + +- NEW_USER: a new user is added +- MEDIA_ADDED: a media is added +- MEDIA_REPORTED: the report for a media was hit diff --git a/docs/User_Scenarios.md b/docs/User_Scenarios.md new file mode 100644 index 0000000..206314d --- /dev/null +++ b/docs/User_Scenarios.md @@ -0,0 +1,20 @@ +## User scenarios to test + + +## test video media + image + try uploading a video + image, make sure they get encoded well and check they appear on index/search/category/author page + try editing/setting metadata, confirm action is performed, also that are searchable + try adding custom poster, confirm it loads well on video page/listings + try specifying different thumbnail time, confirm an automatic screenshot is taken + + +## portal workflow + change workflow to unlisted, check they don't appear on index/search/category/author page + +## users management + create an admin, a MediaCMS editor and MediaCMS manager. All should see edit/delete on a media and also comments, and action should work. + For users edit and delete, only MediaCMS manager and admin should see edit/delete and these actions should work. + +## test subtitle + add language and test subtitling + diff --git a/docs/images/embed.jpg b/docs/images/embed.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5ffbd44029ba00bb2b26ed4a067f4fcd0393ec18 GIT binary patch literal 126732 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 xr| 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
    mi^FvywIam@RioMyXcrW?wGHXCB7$NY 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%0Ds` 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 BqhJ64 literal 0 HcmV?d00001 diff --git a/docs/images/index.jpg b/docs/images/index.jpg new file mode 100644 index 0000000000000000000000000000000000000000..668d9186d882f5211fe54dc13d4b327a13b28cdd GIT binary patch literal 200831 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

    I=#t}tzz@JH`y2r=c5|_YwZ#Iw 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

    ~Pt1?qke}^(p@L#~B2)xVV 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 E*=2(Bd;kCd literal 0 HcmV?d00001 diff --git a/docs/images/video.jpg b/docs/images/video.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fb71b39cca62510cdf72e70c0f10cfe11bc87ced GIT binary patch literal 159849 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�|HqI<(&+avnTe_yzp3GT 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 5 * 1024 * 1024: + raise forms.ValidationError("Image file too large ( > 5mb )") + return image + + def save(self, *args, **kwargs): + data = self.cleaned_data + state = data.get("state") + if state != self.initial["state"]: + self.instance.state = get_next_state( + self.user, self.initial["state"], self.instance.state + ) + + media = super(MediaForm, self).save(*args, **kwargs) + return media + + +class SubtitleForm(forms.ModelForm): + class Meta: + model = Subtitle + fields = ["language", "subtitle_file"] + + def __init__(self, media_item, *args, **kwargs): + super(SubtitleForm, self).__init__(*args, **kwargs) + self.instance.media = media_item + + def save(self, *args, **kwargs): + self.instance.user = self.instance.media.user + media = super(SubtitleForm, self).save(*args, **kwargs) + return media + + +class ContactForm(forms.Form): + from_email = forms.EmailField(required=True) + name = forms.CharField(required=False) + message = forms.CharField(widget=forms.Textarea, required=True) + + def __init__(self, user, *args, **kwargs): + super(ContactForm, self).__init__(*args, **kwargs) + self.fields["name"].label = "Your name:" + self.fields["from_email"].label = "Your email:" + self.fields["message"].label = "Please add your message here and submit:" + self.user = user + if user.is_authenticated: + self.fields.pop("name") + self.fields.pop("from_email") diff --git a/files/helpers.py b/files/helpers.py new file mode 100644 index 0000000..e4c056c --- /dev/null +++ b/files/helpers.py @@ -0,0 +1,754 @@ +# Kudos to Werner Robitza, AVEQ GmbH, for helping with ffmpeg +# related content + +import os +import math +import shutil +import tempfile +import random +import hashlib +import subprocess +import json +from fractions import Fraction +import filetype +from django.conf import settings + + +CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +CRF_ENCODING_NUM_SECONDS = 2 # 0 * 60 # videos with greater duration will get +# CRF encoding and not two-pass +# Encoding individual chunks may yield quality variations if you use a +# too low bitrate, so if you go for the chunk-based variant +# you should use CRF encoding. + +MAX_RATE_MULTIPLIER = 1.5 +BUF_SIZE_MULTIPLIER = 1.5 + +# in seconds, anything between 2 and 6 makes sense +KEYFRAME_DISTANCE = 4 +KEYFRAME_DISTANCE_MIN = 2 + +# speed presets +# see https://trac.ffmpeg.org/wiki/Encode/H.264 +X26x_PRESET = "medium" # "medium" +X265_PRESET = "medium" +X26x_PRESET_BIG_HEIGHT = "faster" + +# VP9_SPEED = 1 # between 0 and 4, lower is slower +VP9_SPEED = 2 + + +VIDEO_CRFS = { + "h264_baseline": 23, + "h264": 23, + "h265": 28, + "vp9": 32, +} + +# video rates for 25 or 60 fps input, for different codecs, in kbps +VIDEO_BITRATES = { + "h264": { + 25: { + 240: 300, + 360: 500, + 480: 1000, + 720: 2500, + 1080: 4500, + 1440: 9000, + 2160: 18000, + }, + 60: {720: 3500, 1080: 7500, 1440: 18000, 2160: 40000}, + }, + "h265": { + 25: { + 240: 150, + 360: 275, + 480: 500, + 720: 1024, + 1080: 1800, + 1440: 4500, + 2160: 10000, + }, + 60: {720: 1800, 1080: 3000, 1440: 8000, 2160: 18000}, + }, + "vp9": { + 25: { + 240: 150, + 360: 275, + 480: 500, + 720: 1024, + 1080: 1800, + 1440: 4500, + 2160: 10000, + }, + 60: {720: 1800, 1080: 3000, 1440: 8000, 2160: 18000}, + }, +} + + +AUDIO_ENCODERS = {"h264": "aac", "h265": "aac", "vp9": "libopus"} + +AUDIO_BITRATES = {"h264": 128, "h265": 128, "vp9": 96} + +EXTENSIONS = {"h264": "mp4", "h265": "mp4", "vp9": "webm"} + +VIDEO_PROFILES = {"h264": "main", "h265": "main"} + + +def get_portal_workflow(): + return settings.PORTAL_WORKFLOW + + +def get_default_state(user=None): + # possible states given the portal workflow setting + state = "private" + if settings.PORTAL_WORKFLOW == "public": + state = "public" + if settings.PORTAL_WORKFLOW == "unlisted": + state = "unlisted" + if settings.PORTAL_WORKFLOW == "private_verified": + if user and user.advancedUser: + state = "unlisted" + return state + + +def get_file_name(filename): + return filename.split("/")[-1] + + +def get_file_type(filename): + if not os.path.exists(filename): + return None + file_type = None + kind = filetype.guess(filename) + if kind is not None: + if kind.mime.startswith("video"): + file_type = "video" + elif kind.mime.startswith("image"): + file_type = "image" + elif kind.mime.startswith("audio"): + file_type = "audio" + elif "pdf" in kind.mime: + file_type = "pdf" + else: + # TODO: do something for files not supported by filetype lib + pass + return file_type + + +def rm_file(filename): + if os.path.isfile(filename): + try: + os.remove(filename) + return True + except OSError: + pass + return False + + +def rm_files(filenames): + if isinstance(filenames, list): + for filename in filenames: + rm_file(filename) + return True + + +def rm_dir(directory): + if os.path.isdir(directory): + # refuse to delete a dir inside project BASE_DIR + if directory.startswith(settings.BASE_DIR): + try: + shutil.rmtree(directory) + return True + except (FileNotFoundError, PermissionError): + pass + return False + + +def url_from_path(filename): + # TODO: find a way to preserver http - https ... + return "{0}{1}".format( + settings.MEDIA_URL, filename.replace(settings.MEDIA_ROOT, "") + ) + + +def create_temp_file(suffix=None, dir=settings.TEMP_DIRECTORY): + tf = tempfile.NamedTemporaryFile(delete=False, suffix=suffix, dir=dir) + return tf.name + + +def create_temp_dir(suffix=None, dir=settings.TEMP_DIRECTORY): + td = tempfile.mkdtemp(dir=dir) + return td + + +def produce_friendly_token(token_len=settings.FRIENDLY_TOKEN_LEN): + token = "" + while len(token) != token_len: + token += CHARS[random.randint(0, len(CHARS) - 1)] + return token + + +def clean_friendly_token(token): + # cleans token + for char in token: + if char not in CHARS: + token.replace(char, "") + return token + + +def mask_ip(ip_address): + return hashlib.md5(ip_address.encode("utf-8")).hexdigest() + + +def run_command(cmd, cwd=None): + """ + Run a command directly + """ + if isinstance(cmd, str): + cmd = cmd.split() + ret = {} + if cwd: + process = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd + ) + else: + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate() + # TODO: catch unicodedecodeerrors here... + if process.returncode == 0: + try: + ret["out"] = stdout.decode("utf-8") + except BaseException: + ret["out"] = "" + try: + ret["error"] = stderr.decode("utf-8") + except BaseException: + ret["error"] = "" + else: + try: + ret["error"] = stderr.decode("utf-8") + except BaseException: + ret["error"] = "" + return ret + + +def media_file_info(input_file): + """ + Get the info about an input file, as determined by ffprobe + + Returns a dict, with the keys: + - `filename`: Filename + - `file_size`: Size of the file in bytes + - `video_duration`: Duration of the video in `s.msec` + - `video_frame_rate`: Framerate in Hz + - `video_bitrate`: Bitrate of the video stream in kBit/s + - `video_width`: Width in pixels + - `video_height`: Height in pixels + - `video_codec`: Video codec + - `audio_duration`: Duration of the audio in `s.msec` + - `audio_sample_rate`: Audio sample rate in Hz + - `audio_codec`: Audio codec name (`aac`) + - `audio_bitrate`: Bitrate of the video stream in kBit/s + + Also returns the video and audio info raw from ffprobe. + """ + ret = {} + + if not os.path.isfile(input_file): + ret["fail"] = True + return ret + + video_info = {} + audio_info = {} + cmd = ["stat", "-c", "%s", input_file] + + stdout = run_command(cmd).get("out") + if stdout: + file_size = int(stdout.strip()) + else: + ret["fail"] = True + return ret + + cmd = ["md5sum", input_file] + stdout = run_command(cmd).get("out") + if stdout: + md5sum = stdout.split()[0] + else: + md5sum = "" + + cmd = [ + settings.FFPROBE_COMMAND, + "-loglevel", + "error", + "-show_streams", + "-show_entries", + "format=format_name", + "-of", + "json", + input_file, + ] + stdout = run_command(cmd).get("out") + try: + info = json.loads(stdout) + except TypeError: + ret["fail"] = True + return ret + + has_video = False + has_audio = False + for stream_info in info["streams"]: + if stream_info["codec_type"] == "video": + video_info = stream_info + has_video = True + if info.get("format") and info["format"].get("format_name", "") in [ + "tty", + "image2", + "image2pipe", + "bin", + "png_pipe", + "gif", + ]: + ret["fail"] = True + return ret + elif stream_info["codec_type"] == "audio": + audio_info = stream_info + has_audio = True + + if not has_video: + ret["is_video"] = False + ret["is_audio"] = has_audio + ret["audio_info"] = audio_info + return ret + + if "duration" in video_info.keys(): + video_duration = float(video_info["duration"]) + elif "tags" in video_info.keys() and "DURATION" in video_info["tags"]: + duration_str = video_info["tags"]["DURATION"] + try: + hms, msec = duration_str.split(".") + except ValueError: + hms, msec = duration_str.split(",") + + total_dur = sum( + int(x) * 60 ** i for i, x in enumerate(reversed(hms.split(":"))) + ) + video_duration = total_dur + float("0." + msec) + else: + # fallback to format, eg for webm + cmd = [ + settings.FFPROBE_COMMAND, + "-loglevel", + "error", + "-show_format", + "-of", + "json", + input_file, + ] + stdout = run_command(cmd).get("out") + format_info = json.loads(stdout)["format"] + try: + video_duration = float(format_info["duration"]) + except KeyError: + ret["fail"] = True + return ret + + if "bit_rate" in video_info.keys(): + video_bitrate = round(float(video_info["bit_rate"]) / 1024.0, 2) + else: + cmd = [ + settings.FFPROBE_COMMAND, + "-loglevel", + "error", + "-select_streams", + "v", + "-show_entries", + "packet=size", + "-of", + "compact=p=0:nk=1", + input_file, + ] + stdout = run_command(cmd).get("out") + stream_size = sum([int(l) for l in stdout.split("\n") if l != ""]) + video_bitrate = round((stream_size * 8 / 1024.0) / video_duration, 2) + + ret = { + "filename": input_file, + "file_size": file_size, + "video_duration": video_duration, + "video_frame_rate": float(Fraction(video_info["r_frame_rate"])), + "video_bitrate": video_bitrate, + "video_width": video_info["width"], + "video_height": video_info["height"], + "video_codec": video_info["codec_name"], + "has_video": has_video, + "has_audio": has_audio, + } + + if has_audio: + audio_duration = 1 + if "duration" in audio_info.keys(): + audio_duration = float(audio_info["duration"]) + elif "tags" in audio_info.keys() and "DURATION" in audio_info["tags"]: + duration_str = audio_info["tags"]["DURATION"] + try: + hms, msec = duration_str.split(".") + except ValueError: + hms, msec = duration_str.split(",") + total_dur = sum( + int(x) * 60 ** i for i, x in enumerate(reversed(hms.split(":"))) + ) + audio_duration = total_dur + float("0." + msec) + else: + # fallback to format, eg for webm + cmd = [ + settings.FFPROBE_COMMAND, + "-loglevel", + "error", + "-show_format", + "-of", + "json", + input_file, + ] + stdout = run_command(cmd).get("out") + format_info = json.loads(stdout)["format"] + audio_duration = float(format_info["duration"]) + + if "bit_rate" in audio_info.keys(): + audio_bitrate = round(float(audio_info["bit_rate"]) / 1024.0, 2) + else: + # fall back to calculating from accumulated frame duration + cmd = [ + settings.FFPROBE_COMMAND, + "-loglevel", + "error", + "-select_streams", + "a", + "-show_entries", + "packet=size", + "-of", + "compact=p=0:nk=1", + input_file, + ] + stdout = run_command(cmd).get("out") + stream_size = sum([int(l) for l in stdout.split("\n") if l != ""]) + audio_bitrate = round((stream_size * 8 / 1024.0) / audio_duration, 2) + + ret.update( + { + "audio_duration": audio_duration, + "audio_sample_rate": audio_info["sample_rate"], + "audio_codec": audio_info["codec_name"], + "audio_bitrate": audio_bitrate, + "audio_channels": audio_info["channels"], + } + ) + + ret["video_info"] = video_info + ret["audio_info"] = audio_info + ret["is_video"] = True + ret["md5sum"] = md5sum + return ret + + +def calculate_seconds(duration): + # returns seconds, given a ffmpeg extracted string + ret = 0 + if isinstance(duration, str): + duration = duration.split(":") + if len(duration) != 3: + return ret + else: + return ret + + ret += int(float(duration[2])) + ret += int(float(duration[1])) * 60 + ret += int(float(duration[0])) * 60 * 60 + return ret + + +def show_file_size(size): + if size: + size = size / 1000000 + size = round(size, 1) + size = "{0}MB".format(str(size)) + return size + + +def get_base_ffmpeg_command( + input_file, + output_file, + has_audio, + codec, + encoder, + audio_encoder, + target_fps, + target_height, + target_rate, + target_rate_audio, + pass_file, + pass_number, + enc_type, + chunk, +): + """Get the base command for a specific codec, height/rate, and pass + + Arguments: + input_file {str} -- input file name + output_file {str} -- output file name + has_audio {bool} -- does the input have audio? + codec {str} -- video codec + encoder {str} -- video encoder + audio_encoder {str} -- audio encoder + target_fps {int} -- target FPS + target_height {int} -- height + target_rate {int} -- target bitrate in kbps + target_rate_audio {int} -- audio target bitrate + pass_file {str} -- path to temp pass file + pass_number {int} -- number of passes + enc_type {str} -- encoding type (twopass or crf) + """ + + target_fps = int(target_fps) + # avoid Frame rate very high for a muxer not efficiently supporting it. + if target_fps > 90: + target_fps = 90 + + base_cmd = [ + settings.FFMPEG_COMMAND, + "-y", + "-i", + input_file, + "-c:v", + encoder, + "-filter:v", + "scale=-2:" + str(target_height) + ",fps=fps=" + str(target_fps), + # always convert to 4:2:0 -- FIXME: this could be also 4:2:2 + # but compatibility will suffer + "-pix_fmt", + "yuv420p", + ] + + if enc_type == "twopass": + base_cmd.extend(["-b:v", str(target_rate) + "k"]) + elif enc_type == "crf": + base_cmd.extend(["-crf", str(VIDEO_CRFS[codec])]) + if encoder == "libvpx-vp9": + base_cmd.extend(["-b:v", str(target_rate) + "k"]) + + if has_audio: + base_cmd.extend( + [ + "-c:a", + audio_encoder, + "-b:a", + str(target_rate_audio) + "k", + # stereo audio only, see https://trac.ffmpeg.org/ticket/5718 + "-ac", + "2", + ] + ) + + # get keyframe distance in frames + keyframe_distance = int(target_fps * KEYFRAME_DISTANCE) + + # start building the command + cmd = base_cmd[:] + + # preset settings + if encoder == "libvpx-vp9": + if pass_number == 1: + speed = 4 + else: + speed = VP9_SPEED + elif encoder in ["libx264"]: + preset = X26x_PRESET + elif encoder in ["libx265"]: + preset = X265_PRESET + if target_height >= 720: + preset = X26x_PRESET_BIG_HEIGHT + + if encoder == "libx264": + level = "4.2" if target_height <= 1080 else "5.2" + + x264_params = [ + "keyint=" + str(keyframe_distance * 2), + "keyint_min=" + str(keyframe_distance), + ] + + cmd.extend( + [ + "-maxrate", + str(int(int(target_rate) * MAX_RATE_MULTIPLIER)) + "k", + "-bufsize", + str(int(int(target_rate) * BUF_SIZE_MULTIPLIER)) + "k", + "-force_key_frames", + "expr:gte(t,n_forced*" + str(KEYFRAME_DISTANCE) + ")", + "-x264-params", + ":".join(x264_params), + "-preset", + preset, + "-profile:v", + VIDEO_PROFILES[codec], + "-level", + level, + ] + ) + + if enc_type == "twopass": + cmd.extend(["-passlogfile", pass_file, "-pass", pass_number]) + + elif encoder == "libx265": + x265_params = [ + "vbv-maxrate=" + str(int(int(target_rate) * MAX_RATE_MULTIPLIER)), + "vbv-bufsize=" + str(int(int(target_rate) * BUF_SIZE_MULTIPLIER)), + "keyint=" + str(keyframe_distance * 2), + "keyint_min=" + str(keyframe_distance), + ] + + if enc_type == "twopass": + x265_params.extend(["stats=" + str(pass_file), "pass=" + str(pass_number)]) + + cmd.extend( + [ + "-force_key_frames", + "expr:gte(t,n_forced*" + str(KEYFRAME_DISTANCE) + ")", + "-x265-params", + ":".join(x265_params), + "-preset", + preset, + "-profile:v", + VIDEO_PROFILES[codec], + ] + ) + elif encoder == "libvpx-vp9": + cmd.extend( + [ + "-g", + str(keyframe_distance), + "-keyint_min", + str(keyframe_distance), + "-maxrate", + str(int(int(target_rate) * MAX_RATE_MULTIPLIER)) + "k", + "-bufsize", + str(int(int(target_rate) * BUF_SIZE_MULTIPLIER)) + "k", + "-speed", + speed, + # '-deadline', 'realtime', + ] + ) + + if enc_type == "twopass": + cmd.extend(["-passlogfile", pass_file, "-pass", pass_number]) + + cmd.extend( + [ + "-strict", + "-2", + ] + ) + + # end of the command + if pass_number == 1: + cmd.extend(["-an", "-f", "null", "/dev/null"]) + elif pass_number == 2: + if output_file.endswith("mp4") and chunk: + cmd.extend(["-movflags", "+faststart"]) + cmd.extend([output_file]) + + return cmd + + +def produce_ffmpeg_commands( + media_file, media_info, resolution, codec, output_filename, pass_file, chunk=False +): + try: + media_info = json.loads(media_info) + except BaseException: + media_info = {} + + if codec == "h264": + encoder = "libx264" + ext = "mp4" + elif codec in ["h265", "hevc"]: + encoder = "libx265" + ext = "mp4" + elif codec == "vp9": + encoder = "libvpx-vp9" + ext = "webm" + else: + return False + + src_framerate = media_info.get("video_frame_rate", 30) + if src_framerate <= 30: + target_rate = VIDEO_BITRATES[codec][25].get(resolution) + else: + target_rate = VIDEO_BITRATES[codec][60].get(resolution) + if not target_rate: # INVESTIGATE MORE! + target_rate = VIDEO_BITRATES[codec][25].get(resolution) + if not target_rate: + return False + + if media_info.get("video_height") < resolution: + if resolution not in [240, 360]: # always get these two + return False + + # if codec == "h264_baseline": + # target_fps = 25 + # else: + + # adjust the target frame rate if the input is fractional + target_fps = ( + src_framerate if isinstance(src_framerate, int) else math.ceil(src_framerate) + ) + + if media_info.get("video_duration") > CRF_ENCODING_NUM_SECONDS: + enc_type = "crf" + else: + enc_type = "twopass" + + if enc_type == "twopass": + passes = [1, 2] + elif enc_type == "crf": + passes = [2] + + cmds = [] + for pass_number in passes: + cmds.append( + get_base_ffmpeg_command( + media_file, + output_file=output_filename, + has_audio=media_info.get("has_audio"), + codec=codec, + encoder=encoder, + audio_encoder=AUDIO_ENCODERS[codec], + target_fps=target_fps, + target_height=resolution, + target_rate=target_rate, + target_rate_audio=AUDIO_BITRATES[codec], + pass_file=pass_file, + pass_number=pass_number, + enc_type=enc_type, + chunk=chunk, + ) + ) + return cmds + + +def clean_query(query): + """This is used to clear text in order to comply with SearchQuery + known exception cases + + :param query: str - the query text that we want to clean + :return: + """ + + if not query: + return "" + + chars = ["^", "{", "}", "&", "|", "<", ">", '"', ")", "(", "!", ":", ";", "'", "#"] + for char in chars: + query = query.replace(char, "") + + return query.lower() diff --git a/files/management_views.py b/files/management_views.py new file mode 100644 index 0000000..4cc36dd --- /dev/null +++ b/files/management_views.py @@ -0,0 +1,195 @@ +from rest_framework.views import APIView +from rest_framework.parsers import JSONParser +from rest_framework.settings import api_settings +from rest_framework.response import Response +from rest_framework import status + +from users.models import User +from users.serializers import UserSerializer +from .permissions import IsMediacmsEditor +from .models import Media, Comment +from .methods import is_mediacms_manager + +from .serializers import MediaSerializer, CommentSerializer + + +class MediaList(APIView): + """Media listings + Used on management pages of MediaCMS + Should be available only to MediaCMS editors, + managers and admins + """ + + permission_classes = (IsMediacmsEditor,) + parser_classes = (JSONParser,) + + def get(self, request, format=None): + params = self.request.query_params + ordering = params.get("ordering", "").strip() + sort_by = params.get("sort_by", "").strip() + state = params.get("state", "").strip() + encoding_status = params.get("encoding_status", "").strip() + media_type = params.get("media_type", "").strip() + + featured = params.get("featured", "").strip() + is_reviewed = params.get("is_reviewed", "").strip() + + sort_by_options = [ + "title", + "add_date", + "edit_date", + "views", + "likes", + "reported_times", + ] + if sort_by not in sort_by_options: + sort_by = "add_date" + if ordering == "asc": + ordering = "" + else: + ordering = "-" + + if media_type not in ["video", "image", "audio", "pdf"]: + media_type = None + + if state not in ["private", "public", "unlisted"]: + state = None + + if encoding_status not in ["pending", "running", "fail", "success"]: + encoding_status = None + + if featured == "true": + featured = True + elif featured == "false": + featured = False + else: + featured = "all" + if is_reviewed == "true": + is_reviewed = True + elif is_reviewed == "false": + is_reviewed = False + else: + is_reviewed = "all" + + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + qs = Media.objects.filter() + if state: + qs = qs.filter(state=state) + if encoding_status: + qs = qs.filter(encoding_status=encoding_status) + if media_type: + qs = qs.filter(media_type=media_type) + + if featured != "all": + qs = qs.filter(featured=featured) + if is_reviewed != "all": + qs = qs.filter(is_reviewed=is_reviewed) + + media = qs.order_by(f"{ordering}{sort_by}") + + paginator = pagination_class() + + page = paginator.paginate_queryset(media, request) + + serializer = MediaSerializer(page, many=True, context={"request": request}) + return paginator.get_paginated_response(serializer.data) + + def delete(self, request, format=None): + tokens = request.GET.get("tokens") + if tokens: + tokens = tokens.split(",") + Media.objects.filter(friendly_token__in=tokens).delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class CommentList(APIView): + """Comments listings + Used on management pages of MediaCMS + Should be available only to MediaCMS editors, + managers and admins + """ + + permission_classes = (IsMediacmsEditor,) + parser_classes = (JSONParser,) + + def get(self, request, format=None): + params = self.request.query_params + ordering = params.get("ordering", "").strip() + sort_by = params.get("sort_by", "").strip() + + sort_by_options = ["text", "add_date"] + if sort_by not in sort_by_options: + sort_by = "add_date" + if ordering == "asc": + ordering = "" + else: + ordering = "-" + + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + + qs = Comment.objects.filter() + media = qs.order_by(f"{ordering}{sort_by}") + + paginator = pagination_class() + + page = paginator.paginate_queryset(media, request) + + serializer = CommentSerializer(page, many=True, context={"request": request}) + return paginator.get_paginated_response(serializer.data) + + + def delete(self, request, format=None): + comment_ids = request.GET.get('comment_ids') + if comment_ids: + comments = comment_ids.split(',') + Comment.objects.filter(uid__in=comments).delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class UserList(APIView): + """Users listings + Used on management pages of MediaCMS + Should be available only to MediaCMS editors, + managers and admins. Delete should be option + for managers+admins only. + """ + + permission_classes = (IsMediacmsEditor,) + parser_classes = (JSONParser,) + + def get(self, request, format=None): + params = self.request.query_params + ordering = params.get("ordering", "").strip() + sort_by = params.get("sort_by", "").strip() + + sort_by_options = ["date_added", "name"] + if sort_by not in sort_by_options: + sort_by = "date_added" + if ordering == "asc": + ordering = "" + else: + ordering = "-" + + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + + qs = User.objects.filter() + media = qs.order_by(f"{ordering}{sort_by}") + + paginator = pagination_class() + + page = paginator.paginate_queryset(media, request) + + serializer = UserSerializer(page, many=True, context={"request": request}) + return paginator.get_paginated_response(serializer.data) + + def delete(self, request, format=None): + if not is_mediacms_manager(request.user): + return Response( + {"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST + ) + + tokens = request.GET.get("tokens") + if tokens: + tokens = tokens.split(",") + User.objects.filter(username__in=tokens).delete() + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/files/methods.py b/files/methods.py new file mode 100644 index 0000000..2bd81f0 --- /dev/null +++ b/files/methods.py @@ -0,0 +1,437 @@ +# Kudos to Werner Robitza, AVEQ GmbH, for helping with ffmpeg +# related content + +import logging +import random +import itertools +from datetime import datetime +from cms import celery_app +from django.conf import settings +from django.core.cache import cache +from django.db.models import Q +from django.core.mail import EmailMessage + +from . import models +from .helpers import mask_ip + +logger = logging.getLogger(__name__) + + +def get_user_or_session(request): + """Return a dictionary with user info + whether user is authenticated or not + this is used in action calculations, example for + increasing the watch counter of a media + """ + + ret = {} + if request.user.is_authenticated: + ret["user_id"] = request.user.id + else: + if not request.session.session_key: + request.session.save() + ret["user_session"] = request.session.session_key + if settings.MASK_IPS_FOR_ACTIONS: + ret["remote_ip_addr"] = mask_ip(request.META.get("REMOTE_ADDR")) + else: + ret["remote_ip_addr"] = request.META.get("REMOTE_ADDR") + return ret + + +def pre_save_action(media, user, session_key, action, remote_ip): + """This will perform some checkes + example threshold checks, before performing an action + """ + + from actions.models import MediaAction + + if user: + query = MediaAction.objects.filter(media=media, action=action, user=user) + else: + query = MediaAction.objects.filter( + media=media, action=action, session_key=session_key + ) + query = query.order_by("-action_date") + + if query: + query = query.first() + if action in ["like", "dislike", "report"]: + return False # has alread done action once + elif action == "watch" and user: + # increase the number of times a media is viewed + if media.duration: + now = datetime.now(query.action_date.tzinfo) + if (now - query.action_date).seconds > media.duration: + return True + else: + if user: # first time action + return True + + if not user: + # perform some checking for requests where no session + # id is specified (and user is anonymous) to avoid spam + # eg allow for the same remote_ip for a specific number of actions + query = ( + MediaAction.objects.filter(media=media, action=action, remote_ip=remote_ip) + .filter(user=None) + .order_by("-action_date") + ) + if query: + query = query.first() + now = datetime.now(query.action_date.tzinfo) + if action == "watch": + if not (now - query.action_date).seconds > media.duration: + return False + if (now - query.action_date).seconds > settings.TIME_TO_ACTION_ANONYMOUS: + return True + else: + return True + + return False + + +def is_mediacms_editor(user): + """Whether user is MediaCMS editor""" + + editor = False + try: + if user.is_superuser or user.is_manager or user.is_editor: + editor = True + except BaseException: + pass + return editor + + +def is_mediacms_manager(user): + """Whether user is MediaCMS manager""" + + manager = False + try: + if user.is_superuser or user.is_manager: + manager = True + except BaseException: + pass + return manager + + +def get_next_state(user, current_state, next_state): + """Return valid state, given a current and next state + and the user object. + Users may themselves perform only allowed transitions + """ + + if next_state not in ["public", "private", "unlisted"]: + next_state = settings.PORTAL_WORKFLOW # get default state + if is_mediacms_editor(user): + # allow any transition + return next_state + + if settings.PORTAL_WORKFLOW == "private": + next_state = "private" + + if settings.PORTAL_WORKFLOW == "unlisted": + # don't allow to make media public in this case + if next_state == "public": + next_state = current_state + + return next_state + + +def notify_users(friendly_token=None, action=None, extra=None): + """Notify users through email, for a set of actions""" + + notify_items = [] + media = None + if friendly_token: + media = models.Media.objects.filter(friendly_token=friendly_token).first() + if not media: + return False + media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url() + + if action == "media_reported" and media: + if settings.ADMINS_NOTIFICATIONS.get("MEDIA_REPORTED", False): + title = "[{}] - Media was reported".format(settings.PORTAL_NAME) + msg = """ +Media %s was reported. +Reason: %s\n +Total times this media has been reported: %s + """ % ( + media_url, + extra, + media.reported_times, + ) + d = {} + d["title"] = title + d["msg"] = msg + d["to"] = settings.ADMIN_EMAIL_LIST + notify_items.append(d) + + if action == "media_added" and media: + if settings.ADMINS_NOTIFICATIONS.get("MEDIA_ADDED", False): + title = "[{}] - Media was added".format(settings.PORTAL_NAME) + msg = """ +Media %s was added by user %s. +""" % ( + media_url, + media.user, + ) + d = {} + d["title"] = title + d["msg"] = msg + d["to"] = settings.ADMIN_EMAIL_LIST + notify_items.append(d) + if settings.USERS_NOTIFICATIONS.get("MEDIA_ADDED", False): + title = "[{}] - Your media was added".format(settings.PORTAL_NAME) + msg = """ +Your media has been added! It will be encoded and will be available soon. +URL: %s + """ % ( + media_url + ) + d = {} + d["title"] = title + d["msg"] = msg + d["to"] = [media.user.email] + notify_items.append(d) + + for item in notify_items: + email = EmailMessage( + item["title"], item["msg"], settings.DEFAULT_FROM_EMAIL, item["to"] + ) + email.send(fail_silently=True) + return True + + +def show_recommended_media(request, limit=100): + """Return a list of recommended media + used on the index page + """ + + basic_query = Q(listable=True) + pmi = cache.get("popular_media_ids") + # produced by task get_list_of_popular_media and cached + if pmi: + media = list( + models.Media.objects.filter(friendly_token__in=pmi) + .filter(basic_query) + .prefetch_related("user")[:limit] + ) + else: + media = list( + models.Media.objects.filter(basic_query) + .order_by("-views", "-likes") + .prefetch_related("user")[:limit] + ) + random.shuffle(media) + return media + + +def show_related_media(media, request=None, limit=100): + """Return a list of related media""" + + if settings.RELATED_MEDIA_STRATEGY == "calculated": + return show_related_media_calculated(media, request, limit) + elif settings.RELATED_MEDIA_STRATEGY == "author": + return show_related_media_author(media, request, limit) + + return show_related_media_content(media, request, limit) + + +def show_related_media_content(media, request, limit): + """Return a list of related media based on simple calculations""" + + # Create list with author items + # then items on same category, then some random(latest) + # Aim is to always show enough (limit) videos + # and include author videos in any case + + q_author = Q(listable=True, user=media.user) + m = list( + models.Media.objects.filter(q_author) + .order_by() + .prefetch_related("user")[:limit] + ) + + # order by random criteria so that it doesn't bring the same results + # attention: only fields that are indexed make sense here! also need + # find a way for indexes with more than 1 field + order_criteria = [ + "-views", + "views", + "add_date", + "-add_date", + "featured", + "-featured", + "user_featured", + "-user_featured", + ] + # TODO: MAke this mess more readable, and add TAGS support - aka related + # tags rather than random media + if len(m) < limit: + category = media.category.first() + if category: + q_category = Q(listable=True, category=category) + q_res = ( + models.Media.objects.filter(q_category) + .order_by(order_criteria[random.randint(0, len(order_criteria) - 1)]) + .prefetch_related("user")[: limit - media.user.media_count] + ) + m = list(itertools.chain(m, q_res)) + + if len(m) < limit: + q_generic = Q(listable=True) + q_res = ( + models.Media.objects.filter(q_generic) + .order_by(order_criteria[random.randint(0, len(order_criteria) - 1)]) + .prefetch_related("user")[: limit - media.user.media_count] + ) + m = list(itertools.chain(m, q_res)) + + m = list(set(m[:limit])) # remove duplicates + + try: + m.remove(media) # remove media from results + except ValueError: + pass + + random.shuffle(m) + return m + + +def show_related_media_author(media, request, limit): + """Return a list of related media form the same author""" + + q_author = Q(listable=True, user=media.user) + m = list( + models.Media.objects.filter(q_author) + .order_by() + .prefetch_related("user")[:limit] + ) + + # order by random criteria so that it doesn't bring the same results + # attention: only fields that are indexed make sense here! also need + # find a way for indexes with more than 1 field + + m = list(set(m[:limit])) # remove duplicates + + try: + m.remove(media) # remove media from results + except ValueError: + pass + + random.shuffle(m) + return m + + +def show_related_media_calculated(media, request, limit): + + """Return a list of related media based on ML recommendations + A big todo! + """ + + return [] + + +def update_user_ratings(user, media, user_ratings): + """Populate user ratings for a media""" + + for rating in user_ratings: + user_rating = ( + models.Rating.objects.filter( + user=user, media_id=media, rating_category_id=rating.get("category_id") + ) + .only("score") + .first() + ) + if user_rating: + rating["score"] = user_rating.score + return user_ratings + + +def notify_user_on_comment(friendly_token): + """Notify users through email, for a set of actions""" + + media = None + media = models.Media.objects.filter(friendly_token=friendly_token).first() + if not media: + return False + + user = media.user + media_url = settings.SSL_FRONTEND_HOST + media.get_absolute_url() + + if user.notification_on_comments: + title = "[{}] - A comment was added".format(settings.PORTAL_NAME) + msg = """ +A comment has been added to your media %s . +View it on %s + """ % ( + media.title, + media_url, + ) + email = EmailMessage( + title, msg, settings.DEFAULT_FROM_EMAIL, [media.user.email] + ) + email.send(fail_silently=True) + return True + + +def list_tasks(): + """Lists celery tasks + To be used in an admin dashboard + """ + + i = celery_app.control.inspect([]) + ret = {} + temp = {} + task_ids = [] + media_profile_pairs = [] + + temp["active"] = i.active() + temp["reserved"] = i.reserved() + temp["scheduled"] = i.scheduled() + + for state, state_dict in temp.items(): + ret[state] = {} + ret[state]["tasks"] = [] + for worker, worker_dict in state_dict.items(): + for task in worker_dict: + task_dict = {} + task_dict["worker"] = worker + task_dict["task_id"] = task.get("id") + task_ids.append(task.get("id")) + task_dict["args"] = task.get("args") + task_dict["name"] = task.get("name") + task_dict["time_start"] = task.get("time_start") + if task.get("name") == "encode_media": + task_args = task.get("args") + for bad in "(),'": + task_args = task_args.replace(bad, "") + friendly_token = task_args.split()[0] + profile_id = task_args.split()[1] + + media = models.Media.objects.filter( + friendly_token=friendly_token + ).first() + if media: + profile = models.EncodeProfile.objects.filter( + id=profile_id + ).first() + if profile: + media_profile_pairs.append( + (media.friendly_token, profile.id) + ) + task_dict["info"] = {} + task_dict["info"]["profile name"] = profile.name + task_dict["info"]["media title"] = media.title + encoding = models.Encoding.objects.filter( + task_id=task.get("id") + ).first() + if encoding: + task_dict["info"][ + "encoding progress" + ] = encoding.progress + + ret[state]["tasks"].append(task_dict) + ret["task_ids"] = task_ids + ret["media_profile_pairs"] = media_profile_pairs + return ret diff --git a/files/migrations/0001_initial.py b/files/migrations/0001_initial.py new file mode 100644 index 0000000..306d9f4 --- /dev/null +++ b/files/migrations/0001_initial.py @@ -0,0 +1,637 @@ +# Generated by Django 3.1.4 on 2020-12-01 07:12 + +import django.contrib.postgres.search +from django.db import migrations, models +import files.models +import imagekit.models.fields +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Category", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("uid", models.UUIDField(default=uuid.uuid4, unique=True)), + ("add_date", models.DateTimeField(auto_now_add=True)), + ("title", models.CharField(db_index=True, max_length=100, unique=True)), + ("description", models.TextField(blank=True)), + ( + "is_global", + models.BooleanField( + default=False, help_text="global categories or user specific" + ), + ), + ( + "media_count", + models.IntegerField(default=0, help_text="number of media"), + ), + ( + "thumbnail", + imagekit.models.fields.ProcessedImageField( + blank=True, upload_to=files.models.category_thumb_path + ), + ), + ( + "listings_thumbnail", + models.CharField( + blank=True, + help_text="Thumbnail to show on listings", + max_length=400, + null=True, + ), + ), + ], + options={ + "verbose_name_plural": "Categories", + "ordering": ["title"], + }, + ), + migrations.CreateModel( + name="Comment", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("add_date", models.DateTimeField(auto_now_add=True)), + ("text", models.TextField(help_text="text")), + ("uid", models.UUIDField(default=uuid.uuid4, unique=True)), + ("lft", models.PositiveIntegerField(editable=False)), + ("rght", models.PositiveIntegerField(editable=False)), + ("tree_id", models.PositiveIntegerField(db_index=True, editable=False)), + ("level", models.PositiveIntegerField(editable=False)), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="EncodeProfile", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=90)), + ( + "extension", + models.CharField( + choices=[("mp4", "mp4"), ("webm", "webm"), ("gif", "gif")], + max_length=10, + ), + ), + ( + "resolution", + models.IntegerField( + blank=True, + choices=[ + (2160, "2160"), + (1440, "1440"), + (1080, "1080"), + (720, "720"), + (480, "480"), + (360, "360"), + (240, "240"), + ], + null=True, + ), + ), + ( + "codec", + models.CharField( + blank=True, + choices=[("h265", "h265"), ("h264", "h264"), ("vp9", "vp9")], + max_length=10, + null=True, + ), + ), + ("description", models.TextField(blank=True, help_text="description")), + ("active", models.BooleanField(default=True)), + ], + options={ + "ordering": ["resolution"], + }, + ), + migrations.CreateModel( + name="Encoding", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("add_date", models.DateTimeField(auto_now_add=True)), + ("commands", models.TextField(blank=True, help_text="commands run")), + ( + "chunk", + models.BooleanField( + db_index=True, default=False, help_text="is chunk?" + ), + ), + ("chunk_file_path", models.CharField(blank=True, max_length=400)), + ("chunks_info", models.TextField(blank=True)), + ("logs", models.TextField(blank=True)), + ("md5sum", models.CharField(blank=True, max_length=50, null=True)), + ( + "media_file", + models.FileField( + blank=True, + max_length=500, + upload_to=files.models.encoding_media_file_path, + verbose_name="encoding file", + ), + ), + ("progress", models.PositiveSmallIntegerField(default=0)), + ("update_date", models.DateTimeField(auto_now=True)), + ("retries", models.IntegerField(default=0)), + ("size", models.CharField(blank=True, max_length=20)), + ( + "status", + models.CharField( + choices=[ + ("pending", "Pending"), + ("running", "Running"), + ("fail", "Fail"), + ("success", "Success"), + ], + default="pending", + max_length=20, + ), + ), + ("temp_file", models.CharField(blank=True, max_length=400)), + ("task_id", models.CharField(blank=True, max_length=100)), + ("total_run_time", models.IntegerField(default=0)), + ("worker", models.CharField(blank=True, max_length=100)), + ], + ), + migrations.CreateModel( + name="Language", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("code", models.CharField(help_text="language code", max_length=12)), + ("title", models.CharField(help_text="language code", max_length=100)), + ], + options={ + "ordering": ["id"], + }, + ), + migrations.CreateModel( + name="License", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=100, unique=True)), + ("description", models.TextField(blank=True)), + ], + ), + migrations.CreateModel( + name="Media", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "add_date", + models.DateTimeField( + blank=True, + db_index=True, + null=True, + verbose_name="Date produced", + ), + ), + ( + "allow_download", + models.BooleanField( + default=True, + help_text="Whether option to download media is shown", + ), + ), + ("description", models.TextField(blank=True)), + ("dislikes", models.IntegerField(default=0)), + ("duration", models.IntegerField(default=0)), + ("edit_date", models.DateTimeField(auto_now=True)), + ( + "enable_comments", + models.BooleanField( + default=True, + help_text="Whether comments will be allowed for this media", + ), + ), + ( + "encoding_status", + models.CharField( + choices=[ + ("pending", "Pending"), + ("running", "Running"), + ("fail", "Fail"), + ("success", "Success"), + ], + db_index=True, + default="pending", + max_length=20, + ), + ), + ( + "featured", + models.BooleanField( + db_index=True, + default=False, + help_text="Whether media is globally featured by a MediaCMS editor", + ), + ), + ( + "friendly_token", + models.CharField( + blank=True, + db_index=True, + help_text="Identifier for the Media", + max_length=12, + ), + ), + ( + "hls_file", + models.CharField( + blank=True, + help_text="Path to HLS file for videos", + max_length=1000, + ), + ), + ( + "is_reviewed", + models.BooleanField( + db_index=True, + default=True, + help_text="Whether media is reviewed, so it can appear on public listings", + ), + ), + ("likes", models.IntegerField(db_index=True, default=1)), + ( + "listable", + models.BooleanField( + default=False, help_text="Whether it will appear on listings" + ), + ), + ( + "md5sum", + models.CharField( + blank=True, + help_text="Not exposed, used internally", + max_length=50, + null=True, + ), + ), + ( + "media_file", + models.FileField( + help_text="media file", + max_length=500, + upload_to=files.models.original_media_file_path, + verbose_name="media file", + ), + ), + ( + "media_info", + models.TextField( + blank=True, help_text="extracted media metadata info" + ), + ), + ( + "media_type", + models.CharField( + blank=True, + choices=[ + ("video", "Video"), + ("image", "Image"), + ("pdf", "Pdf"), + ("audio", "Audio"), + ], + db_index=True, + default="video", + max_length=20, + ), + ), + ( + "password", + models.CharField( + blank=True, + help_text="password for private media", + max_length=100, + ), + ), + ( + "preview_file_path", + models.CharField( + blank=True, + help_text="preview gif for videos, path in filesystem", + max_length=500, + ), + ), + ( + "poster", + imagekit.models.fields.ProcessedImageField( + blank=True, + help_text="media extracted big thumbnail, shown on media page", + max_length=500, + upload_to=files.models.original_thumbnail_file_path, + ), + ), + ( + "reported_times", + models.IntegerField( + default=0, help_text="how many time a Medis is reported" + ), + ), + ( + "search", + django.contrib.postgres.search.SearchVectorField( + help_text="used to store all searchable info and metadata for a Media", + null=True, + ), + ), + ( + "size", + models.CharField( + blank=True, + help_text="media size in bytes, automatically calculated", + max_length=20, + null=True, + ), + ), + ( + "sprites", + models.FileField( + blank=True, + help_text="sprites file, only for videos, displayed on the video player", + max_length=500, + upload_to=files.models.original_thumbnail_file_path, + ), + ), + ( + "state", + models.CharField( + choices=[ + ("private", "Private"), + ("public", "Public"), + ("unlisted", "Unlisted"), + ], + db_index=True, + default="public", + help_text="state of Media", + max_length=20, + ), + ), + ( + "title", + models.CharField( + blank=True, + db_index=True, + help_text="media title", + max_length=100, + ), + ), + ( + "thumbnail", + imagekit.models.fields.ProcessedImageField( + blank=True, + help_text="media extracted small thumbnail, shown on listings", + max_length=500, + upload_to=files.models.original_thumbnail_file_path, + ), + ), + ( + "thumbnail_time", + models.FloatField( + blank=True, + help_text="Time on video that a thumbnail will be taken", + null=True, + ), + ), + ( + "uid", + models.UUIDField( + default=uuid.uuid4, + help_text="A unique identifier for the Media", + unique=True, + ), + ), + ( + "uploaded_thumbnail", + imagekit.models.fields.ProcessedImageField( + blank=True, + help_text="thumbnail from uploaded_poster field", + max_length=500, + upload_to=files.models.original_thumbnail_file_path, + ), + ), + ( + "uploaded_poster", + imagekit.models.fields.ProcessedImageField( + blank=True, + help_text="This image will characterize the media", + max_length=500, + upload_to=files.models.original_thumbnail_file_path, + verbose_name="Upload image", + ), + ), + ( + "user_featured", + models.BooleanField( + default=False, help_text="Featured by the user" + ), + ), + ("video_height", models.IntegerField(default=1)), + ("views", models.IntegerField(db_index=True, default=1)), + ], + options={ + "ordering": ["-add_date"], + }, + ), + migrations.CreateModel( + name="Playlist", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("add_date", models.DateTimeField(auto_now_add=True, db_index=True)), + ("description", models.TextField(blank=True, help_text="description")), + ( + "friendly_token", + models.CharField(blank=True, db_index=True, max_length=12), + ), + ("title", models.CharField(db_index=True, max_length=100)), + ("uid", models.UUIDField(default=uuid.uuid4, unique=True)), + ], + ), + migrations.CreateModel( + name="PlaylistMedia", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("action_date", models.DateTimeField(auto_now=True)), + ("ordering", models.IntegerField(default=1)), + ], + options={ + "ordering": ["ordering", "-action_date"], + }, + ), + migrations.CreateModel( + name="Rating", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("add_date", models.DateTimeField(auto_now_add=True)), + ( + "score", + models.IntegerField(validators=[files.models.validate_rating]), + ), + ], + options={ + "verbose_name_plural": "Ratings", + }, + ), + migrations.CreateModel( + name="RatingCategory", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("description", models.TextField(blank=True)), + ("enabled", models.BooleanField(default=True)), + ("title", models.CharField(db_index=True, max_length=200, unique=True)), + ], + options={ + "verbose_name_plural": "Rating Categories", + }, + ), + migrations.CreateModel( + name="Subtitle", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "subtitle_file", + models.FileField( + help_text="File has to be WebVTT format", + max_length=500, + upload_to=files.models.subtitles_file_path, + verbose_name="Subtitle/CC file", + ), + ), + ], + ), + migrations.CreateModel( + name="Tag", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(db_index=True, max_length=100, unique=True)), + ( + "media_count", + models.IntegerField(default=0, help_text="number of media"), + ), + ( + "listings_thumbnail", + models.CharField( + blank=True, + db_index=True, + help_text="Thumbnail to show on listings", + max_length=400, + null=True, + ), + ), + ], + options={ + "ordering": ["title"], + }, + ), + ] diff --git a/files/migrations/0002_auto_20201201_0712.py b/files/migrations/0002_auto_20201201_0712.py new file mode 100644 index 0000000..6b1b0f4 --- /dev/null +++ b/files/migrations/0002_auto_20201201_0712.py @@ -0,0 +1,240 @@ +# Generated by Django 3.1.4 on 2020-12-01 07:12 + +from django.conf import settings +import django.contrib.postgres.indexes +from django.db import migrations, models +import django.db.models.deletion +import mptt.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("files", "0001_initial"), + ("users", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="tag", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="subtitle", + name="language", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="files.language" + ), + ), + migrations.AddField( + model_name="subtitle", + name="media", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="subtitles", + to="files.media", + ), + ), + migrations.AddField( + model_name="subtitle", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), + ), + migrations.AddField( + model_name="rating", + name="media", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="ratings", + to="files.media", + ), + ), + migrations.AddField( + model_name="rating", + name="rating_category", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="files.ratingcategory" + ), + ), + migrations.AddField( + model_name="rating", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), + ), + migrations.AddField( + model_name="playlistmedia", + name="media", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="files.media" + ), + ), + migrations.AddField( + model_name="playlistmedia", + name="playlist", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="files.playlist" + ), + ), + migrations.AddField( + model_name="playlist", + name="media", + field=models.ManyToManyField( + blank=True, through="files.PlaylistMedia", to="files.Media" + ), + ), + migrations.AddField( + model_name="playlist", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="playlists", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="media", + name="category", + field=models.ManyToManyField( + blank=True, + help_text="Media can be part of one or more categories", + to="files.Category", + ), + ), + migrations.AddField( + model_name="media", + name="channel", + field=models.ForeignKey( + blank=True, + help_text="Media can exist in one or no Channels", + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="users.channel", + ), + ), + migrations.AddField( + model_name="media", + name="license", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="files.license", + ), + ), + migrations.AddField( + model_name="media", + name="rating_category", + field=models.ManyToManyField( + blank=True, + help_text="Rating category, if media Rating is allowed", + to="files.RatingCategory", + ), + ), + migrations.AddField( + model_name="media", + name="tags", + field=models.ManyToManyField( + blank=True, + help_text="select one or more out of the existing tags", + to="files.Tag", + ), + ), + migrations.AddField( + model_name="media", + name="user", + field=models.ForeignKey( + help_text="user that uploads the media", + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="encoding", + name="media", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="encodings", + to="files.media", + ), + ), + migrations.AddField( + model_name="encoding", + name="profile", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="files.encodeprofile" + ), + ), + migrations.AddField( + model_name="comment", + name="media", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="comments", + to="files.media", + ), + ), + migrations.AddField( + model_name="comment", + name="parent", + field=mptt.fields.TreeForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="children", + to="files.comment", + ), + ), + migrations.AddField( + model_name="comment", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), + ), + migrations.AddField( + model_name="category", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddIndex( + model_name="rating", + index=models.Index( + fields=["user", "media"], name="files_ratin_user_id_72ca6a_idx" + ), + ), + migrations.AlterUniqueTogether( + name="rating", + unique_together={("user", "media", "rating_category")}, + ), + migrations.AddIndex( + model_name="media", + index=models.Index( + fields=["state", "encoding_status", "is_reviewed"], + name="files_media_state_666b93_idx", + ), + ), + migrations.AddIndex( + model_name="media", + index=django.contrib.postgres.indexes.GinIndex( + fields=["search"], name="files_media_search_7194c6_gin" + ), + ), + ] diff --git a/files/migrations/__init__.py b/files/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/files/models.py b/files/models.py new file mode 100644 index 0000000..65ec6b4 --- /dev/null +++ b/files/models.py @@ -0,0 +1,1714 @@ +import logging +import uuid +import os +import re +import tempfile +import random +import json +import m3u8 +from django.utils import timezone +from django.db import connection +from django.db import models +from django.template.defaultfilters import slugify +from django.conf import settings +from django.contrib.postgres.indexes import GinIndex +from django.db.models.signals import pre_delete, post_delete, post_save, m2m_changed +from django.core.files import File +from django.core.exceptions import ValidationError +from django.dispatch import receiver +from django.urls import reverse +from django.utils.html import strip_tags +from django.contrib.postgres.search import SearchVectorField +from mptt.models import MPTTModel, TreeForeignKey + +from imagekit.processors import ResizeToFit +from imagekit.models import ProcessedImageField + +from . import helpers +from .methods import notify_users +from .stop_words import STOP_WORDS + +logger = logging.getLogger(__name__) + +RE_TIMECODE = re.compile(r"(\d+:\d+:\d+.\d+)") + +# this is used by Media and Encoding models +# reflects media encoding status for objects +MEDIA_ENCODING_STATUS = ( + ("pending", "Pending"), + ("running", "Running"), + ("fail", "Fail"), + ("success", "Success"), +) + +# the media state of a Media object +# this is set by default according to the portal workflow +MEDIA_STATES = ( + ("private", "Private"), + ("public", "Public"), + ("unlisted", "Unlisted"), +) + +# each uploaded Media gets a media_type hint +# by helpers.get_file_type + +MEDIA_TYPES_SUPPORTED = ( + ("video", "Video"), + ("image", "Image"), + ("pdf", "Pdf"), + ("audio", "Audio"), +) + +ENCODE_EXTENSIONS = ( + ("mp4", "mp4"), + ("webm", "webm"), + ("gif", "gif"), +) + +ENCODE_RESOLUTIONS = ( + (2160, "2160"), + (1440, "1440"), + (1080, "1080"), + (720, "720"), + (480, "480"), + (360, "360"), + (240, "240"), +) + +CODECS = ( + ("h265", "h265"), + ("h264", "h264"), + ("vp9", "vp9"), +) + +ENCODE_EXTENSIONS_KEYS = [extension for extension, name in ENCODE_EXTENSIONS] +ENCODE_RESOLUTIONS_KEYS = [resolution for resolution, name in ENCODE_RESOLUTIONS] + + +def original_media_file_path(instance, filename): + """Helper function to place original media file""" + file_name = "{0}.{1}".format(instance.uid.hex, helpers.get_file_name(filename)) + return settings.MEDIA_UPLOAD_DIR + "user/{0}/{1}".format( + instance.user.username, file_name + ) + + +def encoding_media_file_path(instance, filename): + """Helper function to place encoded media file""" + + file_name = "{0}.{1}".format( + instance.media.uid.hex, helpers.get_file_name(filename) + ) + return settings.MEDIA_ENCODING_DIR + "{0}/{1}/{2}".format( + instance.profile.id, instance.media.user.username, file_name + ) + + +def original_thumbnail_file_path(instance, filename): + """Helper function to place original media thumbnail file""" + + return settings.THUMBNAIL_UPLOAD_DIR + "user/{0}/{1}".format( + instance.user.username, filename + ) + + +def subtitles_file_path(instance, filename): + """Helper function to place subtitle file""" + + return settings.SUBTITLES_UPLOAD_DIR + "user/{0}/{1}".format( + instance.media.user.username, filename + ) + + +def category_thumb_path(instance, filename): + """Helper function to place category thumbnail file""" + + file_name = "{0}.{1}".format(instance.uid.hex, helpers.get_file_name(filename)) + return settings.MEDIA_UPLOAD_DIR + "categories/{0}".format(file_name) + + +class Media(models.Model): + """The most important model for MediaCMS""" + + add_date = models.DateTimeField( + "Date produced", blank=True, null=True, db_index=True + ) + + allow_download = models.BooleanField( + default=True, help_text="Whether option to download media is shown" + ) + + category = models.ManyToManyField( + "Category", blank=True, help_text="Media can be part of one or more categories" + ) + + channel = models.ForeignKey( + "users.Channel", + on_delete=models.CASCADE, + blank=True, + null=True, + help_text="Media can exist in one or no Channels", + ) + + description = models.TextField(blank=True) + + dislikes = models.IntegerField(default=0) + + duration = models.IntegerField(default=0) + + edit_date = models.DateTimeField(auto_now=True) + + enable_comments = models.BooleanField( + default=True, help_text="Whether comments will be allowed for this media" + ) + + encoding_status = models.CharField( + max_length=20, choices=MEDIA_ENCODING_STATUS, default="pending", db_index=True + ) + + featured = models.BooleanField( + default=False, + db_index=True, + help_text="Whether media is globally featured by a MediaCMS editor", + ) + + friendly_token = models.CharField( + blank=True, max_length=12, db_index=True, help_text="Identifier for the Media" + ) + + hls_file = models.CharField( + max_length=1000, blank=True, help_text="Path to HLS file for videos" + ) + + is_reviewed = models.BooleanField( + default=settings.MEDIA_IS_REVIEWED, + db_index=True, + help_text="Whether media is reviewed, so it can appear on public listings", + ) + + license = models.ForeignKey( + "License", on_delete=models.CASCADE, db_index=True, blank=True, null=True + ) + + likes = models.IntegerField(db_index=True, default=1) + + listable = models.BooleanField( + default=False, help_text="Whether it will appear on listings" + ) + + md5sum = models.CharField( + max_length=50, blank=True, null=True, help_text="Not exposed, used internally" + ) + + media_file = models.FileField( + "media file", + upload_to=original_media_file_path, + max_length=500, + help_text="media file", + ) + + media_info = models.TextField(blank=True, help_text="extracted media metadata info") + + media_type = models.CharField( + max_length=20, + blank=True, + choices=MEDIA_TYPES_SUPPORTED, + db_index=True, + default="video", + ) + + password = models.CharField( + max_length=100, blank=True, help_text="password for private media" + ) + + preview_file_path = models.CharField( + max_length=500, + blank=True, + help_text="preview gif for videos, path in filesystem", + ) + + poster = ProcessedImageField( + upload_to=original_thumbnail_file_path, + processors=[ResizeToFit(width=720, height=None)], + format="JPEG", + options={"quality": 95}, + blank=True, + max_length=500, + help_text="media extracted big thumbnail, shown on media page", + ) + + rating_category = models.ManyToManyField( + "RatingCategory", + blank=True, + help_text="Rating category, if media Rating is allowed", + ) + + reported_times = models.IntegerField( + default=0, help_text="how many time a Medis is reported" + ) + + search = SearchVectorField( + null=True, + help_text="used to store all searchable info and metadata for a Media", + ) + + size = models.CharField( + max_length=20, + blank=True, + null=True, + help_text="media size in bytes, automatically calculated", + ) + + sprites = models.FileField( + upload_to=original_thumbnail_file_path, + blank=True, + max_length=500, + help_text="sprites file, only for videos, displayed on the video player", + ) + + state = models.CharField( + max_length=20, + choices=MEDIA_STATES, + default=helpers.get_portal_workflow(), + db_index=True, + help_text="state of Media", + ) + + tags = models.ManyToManyField( + "Tag", blank=True, help_text="select one or more out of the existing tags" + ) + + title = models.CharField( + max_length=100, help_text="media title", blank=True, db_index=True + ) + + thumbnail = ProcessedImageField( + upload_to=original_thumbnail_file_path, + processors=[ResizeToFit(width=344, height=None)], + format="JPEG", + options={"quality": 95}, + blank=True, + max_length=500, + help_text="media extracted small thumbnail, shown on listings", + ) + + thumbnail_time = models.FloatField( + blank=True, null=True, help_text="Time on video that a thumbnail will be taken" + ) + + uid = models.UUIDField( + unique=True, default=uuid.uuid4, help_text="A unique identifier for the Media" + ) + + uploaded_thumbnail = ProcessedImageField( + upload_to=original_thumbnail_file_path, + processors=[ResizeToFit(width=344, height=None)], + format="JPEG", + options={"quality": 85}, + blank=True, + max_length=500, + help_text="thumbnail from uploaded_poster field", + ) + + uploaded_poster = ProcessedImageField( + verbose_name="Upload image", + help_text="This image will characterize the media", + upload_to=original_thumbnail_file_path, + processors=[ResizeToFit(width=720, height=None)], + format="JPEG", + options={"quality": 85}, + blank=True, + max_length=500, + ) + + user = models.ForeignKey( + "users.User", on_delete=models.CASCADE, help_text="user that uploads the media" + ) + + user_featured = models.BooleanField(default=False, help_text="Featured by the user") + + video_height = models.IntegerField(default=1) + + views = models.IntegerField(db_index=True, default=1) + + # keep track if media file has changed, on saves + __original_media_file = None + __original_thumbnail_time = None + __original_uploaded_poster = None + + class Meta: + ordering = ["-add_date"] + indexes = [ + # TODO: check with pgdash.io or other tool what index need be + # removed + GinIndex(fields=["search"]) + ] + + def __str__(self): + return self.title + + def __init__(self, *args, **kwargs): + super(Media, self).__init__(*args, **kwargs) + # keep track if media file has changed, on saves + # thus know when another media was uploaded + # or when thumbnail time change - for videos to + # grep for thumbnail, or even when a new image + # was added as the media poster + self.__original_media_file = self.media_file + self.__original_thumbnail_time = self.thumbnail_time + self.__original_uploaded_poster = self.uploaded_poster + + def save(self, *args, **kwargs): + + if not self.title: + self.title = self.media_file.path.split("/")[-1] + + strip_text_items = ["title", "description"] + for item in strip_text_items: + setattr(self, item, strip_tags(getattr(self, item, None))) + self.title = self.title[:99] + + # if thumbnail_time specified, keep up to single digit + if self.thumbnail_time: + self.thumbnail_time = round(self.thumbnail_time, 1) + + # by default get an add_date of now + if not self.add_date: + self.add_date = timezone.now() + + if not self.friendly_token: + # get a unique identifier + while True: + friendly_token = helpers.produce_friendly_token() + if not Media.objects.filter(friendly_token=friendly_token): + self.friendly_token = friendly_token + break + + if self.pk: + # media exists + + # check case where another media file was uploaded + if self.media_file != self.__original_media_file: + # set this otherwise gets to infinite loop + self.__original_media_file = self.media_file + self.media_init() + + # for video files, if user specified a different time + # to automatically grub thumbnail + if self.thumbnail_time != self.__original_thumbnail_time: + self.__original_thumbnail_time = self.thumbnail_time + self.set_thumbnail(force=True) + else: + # media is going to be created now + # after media is saved, post_save signal will call media_init function + # to take care of post save steps + + self.state = helpers.get_default_state(user=self.user) + + # condition to appear on listings + if ( + self.state == "public" + and self.encoding_status == "success" + and self.is_reviewed == True + ): + self.listable = True + else: + self.listable = False + + super(Media, self).save(*args, **kwargs) + + # produce a thumbnail out of an uploaded poster + # will run only when a poster is uploaded for the first time + if ( + self.uploaded_poster + and self.uploaded_poster != self.__original_uploaded_poster + ): + with open(self.uploaded_poster.path, "rb") as f: + + # set this otherwise gets to infinite loop + self.__original_uploaded_poster = self.uploaded_poster + + myfile = File(f) + thumbnail_name = helpers.get_file_name(self.uploaded_poster.path) + self.uploaded_thumbnail.save(content=myfile, name=thumbnail_name) + + def update_search_vector(self): + """ + Update SearchVector field of SearchModel using raw SQL + search field is used to store SearchVector + """ + db_table = self._meta.db_table + + # first get anything interesting out of the media + # that needs to be search able + + a_tags = b_tags = "" + if self.id: + a_tags = " ".join([tag.title for tag in self.tags.all()]) + b_tags = " ".join([tag.title.replace("-", " ") for tag in self.tags.all()]) + + items = [ + helpers.clean_query(self.title), + self.user.username, + self.user.email, + self.user.name, + helpers.clean_query(self.description), + a_tags, + b_tags, + ] + items = [item for item in items if item] + text = " ".join(items) + text = " ".join( + [token for token in text.lower().split(" ") if token not in STOP_WORDS] + ) + + sql_code = """ + UPDATE {db_table} SET search = to_tsvector( + '{config}', '{text}' + ) WHERE {db_table}.id = {id} + """.format( + db_table=db_table, config="simple", text=text, id=self.id + ) + + try: + with connection.cursor() as cursor: + cursor.execute(sql_code) + except BaseException: + pass # TODO:add log + return True + + def media_init(self): + """Normally this is called when a media is uploaded + Performs all related tasks, as check for media type, + video duration, encode + """ + + self.set_media_type() + if self.media_type == "video": + self.set_thumbnail(force=True) + self.produce_sprite_from_video() + self.encode() + elif self.media_type == "image": + self.set_thumbnail(force=True) + return True + + def set_media_type(self, save=True): + """Sets media type on Media + Set encoding_status as success for non video + content since all listings filter for encoding_status success + """ + + kind = helpers.get_file_type(self.media_file.path) + if kind is not None: + if kind == "image": + self.media_type = "image" + elif kind == "pdf": + self.media_type = "pdf" + + if self.media_type in ["image", "pdf"]: + self.encoding_status = "success" + else: + ret = helpers.media_file_info(self.media_file.path) + if ret.get("fail"): + self.media_type = "" + self.encoding_status = "fail" + elif ret.get("is_video") or ret.get("is_audio"): + try: + self.media_info = json.dumps(ret) + except TypeError: + self.media_info = "" + self.md5sum = ret.get("md5sum") + self.size = helpers.show_file_size(ret.get("file_size")) + else: + self.media_type = "" + self.encoding_status = "fail" + + if ret.get("is_video"): + # case where Media is video. try to set useful + # metadata as duration/height + self.media_type = "video" + self.duration = int(round(float(ret.get("video_duration", 0)))) + self.video_height = int(ret.get("video_height")) + elif ret.get("is_audio"): + self.media_type = "audio" + self.duration = int(float(ret.get("audio_info", {}).get("duration", 0))) + self.encoding_status = "success" + + if save: + self.save( + update_fields=[ + "listable", + "media_type", + "duration", + "media_info", + "video_height", + "size", + "md5sum", + "encoding_status", + ] + ) + return True + + def set_thumbnail(self, force=False): + """sets thumbnail for media + For video call function to produce thumbnail and poster + For image save thumbnail and poster, this will perform + resize action + """ + if force or (not self.thumbnail): + if self.media_type == "video": + self.produce_thumbnails_from_video() + if self.media_type == "image": + with open(self.media_file.path, "rb") as f: + myfile = File(f) + thumbnail_name = ( + helpers.get_file_name(self.media_file.path) + ".jpg" + ) + self.thumbnail.save(content=myfile, name=thumbnail_name) + self.poster.save(content=myfile, name=thumbnail_name) + return True + + def produce_thumbnails_from_video(self): + """Produce thumbnail and poster for media + Only for video types. Uses ffmpeg + """ + if not self.media_type == "video": + return False + + if self.thumbnail_time and 0 <= self.thumbnail_time < self.duration: + thumbnail_time = self.thumbnail_time + else: + thumbnail_time = round(random.uniform(0, self.duration - 0.1), 1) + self.thumbnail_time = thumbnail_time # so that it gets saved + + tf = helpers.create_temp_file(suffix=".jpg") + command = [ + settings.FFMPEG_COMMAND, + "-ss", + str( + thumbnail_time + ), # -ss need to be firt here otherwise time taken is huge + "-i", + self.media_file.path, + "-vframes", + "1", + "-y", + tf, + ] + ret = helpers.run_command(command) + + if os.path.exists(tf) and helpers.get_file_type(tf) == "image": + with open(tf, "rb") as f: + myfile = File(f) + thumbnail_name = helpers.get_file_name(self.media_file.path) + ".jpg" + self.thumbnail.save(content=myfile, name=thumbnail_name) + self.poster.save(content=myfile, name=thumbnail_name) + helpers.rm_file(tf) + return True + + def produce_sprite_from_video(self): + """Start a task that will produce a sprite file + To be used on the video player + """ + + from . import tasks + + tasks.produce_sprite_from_video.delay(self.friendly_token) + return True + + def encode(self, profiles=[], force=True, chunkize=True): + """Start video encoding tasks + Create a task per EncodeProfile object, after checking height + so that no EncodeProfile for highter heights than the video + are created + """ + + if not profiles: + profiles = EncodeProfile.objects.filter(active=True) + profiles = list(profiles) + + from . import tasks + + # attempt to break media file in chunks + if self.duration > settings.CHUNKIZE_VIDEO_DURATION and chunkize: + + for profile in profiles: + + if profile.extension == "gif": + profiles.remove(profile) + encoding = Encoding(media=self, profile=profile) + encoding.save() + enc_url = settings.SSL_FRONTEND_HOST + encoding.get_absolute_url() + tasks.encode_media.apply_async( + args=[self.friendly_token, profile.id, encoding.id, enc_url], + kwargs={"force": force}, + priority=0, + ) + profiles = [p.id for p in profiles] + tasks.chunkize_media.delay(self.friendly_token, profiles, force=force) + else: + for profile in profiles: + if profile.extension != "gif": + if self.video_height and self.video_height < profile.resolution: + if ( + profile.resolution + not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE + ): + continue + encoding = Encoding(media=self, profile=profile) + encoding.save() + enc_url = settings.SSL_FRONTEND_HOST + encoding.get_absolute_url() + if profile.resolution in settings.MINIMUM_RESOLUTIONS_TO_ENCODE: + priority = 9 + else: + priority = 0 + tasks.encode_media.apply_async( + args=[self.friendly_token, profile.id, encoding.id, enc_url], + kwargs={"force": force}, + priority=priority, + ) + + return True + + def post_encode_actions(self, encoding=None, action=None): + """perform things after encode has run + whether it has failed or succeeded + """ + + self.set_encoding_status() + + # set a preview url + if encoding: + if self.media_type == "video" and encoding.profile.extension == "gif": + if action == "delete": + self.preview_file_path = "" + else: + self.preview_file_path = encoding.media_file.path + self.save(update_fields=["listable", "preview_file_path"]) + + self.save(update_fields=["encoding_status", "listable"]) + + if ( + encoding + and encoding.status == "success" + and encoding.profile.codec == "h264" + and action == "add" + ): + from . import tasks + + tasks.create_hls(self.friendly_token) + + return True + + def set_encoding_status(self): + """Set encoding_status for videos + Set success if at least one mp4 exists + """ + mp4_statuses = set( + encoding.status + for encoding in self.encodings.filter(profile__extension="mp4", chunk=False) + ) + + if not mp4_statuses: + encoding_status = "pending" + elif "success" in mp4_statuses: + encoding_status = "success" + elif "running" in mp4_statuses: + encoding_status = "running" + else: + encoding_status = "fail" + self.encoding_status = encoding_status + + return True + + @property + def encodings_info(self, full=False): + """Property used on serializers""" + + ret = {} + chunks_ret = {} + + if self.media_type not in ["video"]: + return ret + for key in ENCODE_RESOLUTIONS_KEYS: + ret[key] = {} + for encoding in self.encodings.select_related("profile").filter(chunk=False): + if encoding.profile.extension == "gif": + continue + enc = self.get_encoding_info(encoding, full=full) + resolution = encoding.profile.resolution + ret[resolution][encoding.profile.codec] = enc + + # TODO: the following code is untested/needs optimization + + # if a file is broken in chunks and they are being + # encoded, the final encoding file won't appear until + # they are finished. Thus, produce the info for these + if full: + extra = [] + for encoding in self.encodings.select_related("profile").filter(chunk=True): + resolution = encoding.profile.resolution + if not ret[resolution].get(encoding.profile.codec): + extra.append(encoding.profile.codec) + for codec in extra: + ret[resolution][codec] = {} + v = self.encodings.filter(chunk=True, profile__codec=codec).values( + "progress" + ) + ret[resolution][codec]["progress"] = ( + sum([p["progress"] for p in v]) / v.count() + ) + # TODO; status/logs/errors + return ret + + def get_encoding_info(self, encoding, full=False): + """Property used on serializers""" + + ep = {} + ep["title"] = encoding.profile.name + ep["url"] = encoding.media_encoding_url + ep["progress"] = encoding.progress + ep["size"] = encoding.size + ep["encoding_id"] = encoding.id + ep["status"] = encoding.status + + if full: + ep["logs"] = encoding.logs + ep["worker"] = encoding.worker + ep["retries"] = encoding.retries + if encoding.total_run_time: + ep["total_run_time"] = encoding.total_run_time + if encoding.commands: + ep["commands"] = encoding.commands + ep["time_started"] = encoding.add_date + ep["updated_time"] = encoding.update_date + return ep + + @property + def categories_info(self): + """Property used on serializers""" + + ret = [] + for cat in self.category.all(): + ret.append({"title": cat.title, "url": cat.get_absolute_url()}) + return ret + + @property + def tags_info(self): + """Property used on serializers""" + + ret = [] + for tag in self.tags.all(): + ret.append({"title": tag.title, "url": tag.get_absolute_url()}) + return ret + + @property + def original_media_url(self): + """Property used on serializers""" + + if settings.SHOW_ORIGINAL_MEDIA: + return helpers.url_from_path(self.media_file.path) + else: + return None + + @property + def thumbnail_url(self): + """Property used on serializers + Prioritize uploaded_thumbnail, if exists, then thumbnail + that is auto-generated + """ + + if self.uploaded_thumbnail: + return helpers.url_from_path(self.uploaded_thumbnail.path) + if self.thumbnail: + return helpers.url_from_path(self.thumbnail.path) + return None + + @property + def poster_url(self): + """Property used on serializers + Prioritize uploaded_poster, if exists, then poster + that is auto-generated + """ + + if self.uploaded_poster: + return helpers.url_from_path(self.uploaded_poster.path) + if self.poster: + return helpers.url_from_path(self.poster.path) + return None + + @property + def subtitles_info(self): + """Property used on serializers + Returns subtitles info + """ + + ret = [] + for subtitle in self.subtitles.all(): + ret.append( + { + "src": helpers.url_from_path(subtitle.subtitle_file.path), + "srclang": subtitle.language.code, + "label": subtitle.language.title, + } + ) + return ret + + @property + def sprites_url(self): + """Property used on serializers + Returns sprites url + """ + + if self.sprites: + return helpers.url_from_path(self.sprites.path) + return None + + @property + def preview_url(self): + """Property used on serializers + Returns preview url + """ + + if self.preview_file_path: + return helpers.url_from_path(self.preview_file_path) + + # get preview_file out of the encodings, since some times preview_file_path + # is empty but there is the gif encoding! + preview_media = self.encodings.filter(profile__extension="gif").first() + if preview_media and preview_media.media_file: + return helpers.url_from_path(preview_media.media_file.path) + return None + + @property + def hls_info(self): + """Property used on serializers + Returns hls info, curated to be read by video.js + """ + + res = {} + if self.hls_file: + if os.path.exists(self.hls_file): + hls_file = self.hls_file + p = os.path.dirname(hls_file) + m3u8_obj = m3u8.load(hls_file) + if os.path.exists(hls_file): + res["master_file"] = helpers.url_from_path(hls_file) + for iframe_playlist in m3u8_obj.iframe_playlists: + uri = os.path.join(p, iframe_playlist.uri) + if os.path.exists(uri): + resolution = iframe_playlist.iframe_stream_info.resolution[ + 1 + ] + res["{}_iframe".format(resolution)] = helpers.url_from_path( + uri + ) + for playlist in m3u8_obj.playlists: + uri = os.path.join(p, playlist.uri) + if os.path.exists(uri): + resolution = playlist.stream_info.resolution[1] + res[ + "{}_playlist".format(resolution) + ] = helpers.url_from_path(uri) + return res + + @property + def author_name(self): + return self.user.name + + @property + def author_username(self): + return self.user.username + + def author_profile(self): + return self.user.get_absolute_url() + + def author_thumbnail(self): + return helpers.url_from_path(self.user.logo.path) + + def get_absolute_url(self, api=False, edit=False): + if edit: + return reverse("edit_media") + "?m={0}".format(self.friendly_token) + if api: + return reverse( + "api_get_media", kwargs={"friendly_token": self.friendly_token} + ) + else: + return reverse("get_media") + "?m={0}".format(self.friendly_token) + + @property + def edit_url(self): + return self.get_absolute_url(edit=True) + + @property + def add_subtitle_url(self): + return "/add_subtitle?m=%s" % self.friendly_token + + @property + def ratings_info(self): + """Property used on ratings + If ratings functionality enabled + """ + + # to be used if user ratings are allowed + ret = [] + if not settings.ALLOW_RATINGS: + return [] + for category in self.rating_category.filter(enabled=True): + ret.append( + { + "score": -1, + # default score, means no score. In case user has already + # rated for this media, it will be populated + "category_id": category.id, + "category_title": category.title, + } + ) + return ret + + +class License(models.Model): + """A Base license model to be used in Media""" + + title = models.CharField(max_length=100, unique=True) + description = models.TextField(blank=True) + + def __str__(self): + return self.title + + +class Category(models.Model): + """A Category base model""" + + uid = models.UUIDField(unique=True, default=uuid.uuid4) + + add_date = models.DateTimeField(auto_now_add=True) + + title = models.CharField(max_length=100, unique=True, db_index=True) + + description = models.TextField(blank=True) + + user = models.ForeignKey( + "users.User", on_delete=models.CASCADE, blank=True, null=True + ) + + is_global = models.BooleanField( + default=False, help_text="global categories or user specific" + ) + + media_count = models.IntegerField(default=0, help_text="number of media") + + thumbnail = ProcessedImageField( + upload_to=category_thumb_path, + processors=[ResizeToFit(width=344, height=None)], + format="JPEG", + options={"quality": 85}, + blank=True, + ) + + listings_thumbnail = models.CharField( + max_length=400, blank=True, null=True, help_text="Thumbnail to show on listings" + ) + + def __str__(self): + return self.title + + class Meta: + ordering = ["title"] + verbose_name_plural = "Categories" + + def get_absolute_url(self): + return reverse("search") + "?c={0}".format(self.title) + + def update_category_media(self): + """Set media_count""" + + self.media_count = Media.objects.filter(listable=True, category=self).count() + self.save(update_fields=["media_count"]) + return True + + @property + def thumbnail_url(self): + """Return thumbnail for category + prioritize processed value of listings_thumbnail + then thumbnail + """ + + if self.listings_thumbnail: + return self.listings_thumbnail + if self.thumbnail: + return helpers.url_from_path(self.thumbnail.path) + + media = ( + Media.objects.filter(category=self, state="public") + .order_by("-views") + .first() + ) + if media: + return media.thumbnail_url + + return None + + def save(self, *args, **kwargs): + strip_text_items = ["title", "description"] + for item in strip_text_items: + setattr(self, item, strip_tags(getattr(self, item, None))) + super(Category, self).save(*args, **kwargs) + + +class Tag(models.Model): + """A Tag model""" + + title = models.CharField(max_length=100, unique=True, db_index=True) + + user = models.ForeignKey( + "users.User", on_delete=models.CASCADE, blank=True, null=True + ) + + media_count = models.IntegerField(default=0, help_text="number of media") + + listings_thumbnail = models.CharField( + max_length=400, + blank=True, + null=True, + help_text="Thumbnail to show on listings", + db_index=True, + ) + + def __str__(self): + return self.title + + class Meta: + ordering = ["title"] + + def get_absolute_url(self): + return reverse("search") + "?t={0}".format(self.title) + + def update_tag_media(self): + self.media_count = Media.objects.filter( + state="public", is_reviewed=True, tags=self + ).count() + self.save(update_fields=["media_count"]) + return True + + def save(self, *args, **kwargs): + self.title = slugify(self.title[:99]) + strip_text_items = ["title"] + for item in strip_text_items: + setattr(self, item, strip_tags(getattr(self, item, None))) + super(Tag, self).save(*args, **kwargs) + + @property + def thumbnail_url(self): + if self.listings_thumbnail: + return self.listings_thumbnail + media = ( + Media.objects.filter(tags=self, state="public").order_by("-views").first() + ) + if media: + return media.thumbnail_url + + return None + + +class EncodeProfile(models.Model): + """Encode Profile model + keeps information for each profile + """ + + name = models.CharField(max_length=90) + + extension = models.CharField(max_length=10, choices=ENCODE_EXTENSIONS) + + resolution = models.IntegerField(choices=ENCODE_RESOLUTIONS, blank=True, null=True) + + codec = models.CharField(max_length=10, choices=CODECS, blank=True, null=True) + + description = models.TextField(blank=True, help_text="description") + + active = models.BooleanField(default=True) + + def __str__(self): + return self.name + + class Meta: + ordering = ["resolution"] + + +class Encoding(models.Model): + """Encoding Media Instances""" + + add_date = models.DateTimeField(auto_now_add=True) + + commands = models.TextField(blank=True, help_text="commands run") + + chunk = models.BooleanField(default=False, db_index=True, help_text="is chunk?") + + chunk_file_path = models.CharField(max_length=400, blank=True) + + chunks_info = models.TextField(blank=True) + + logs = models.TextField(blank=True) + + md5sum = models.CharField(max_length=50, blank=True, null=True) + + media = models.ForeignKey(Media, on_delete=models.CASCADE, related_name="encodings") + + media_file = models.FileField( + "encoding file", upload_to=encoding_media_file_path, blank=True, max_length=500 + ) + + profile = models.ForeignKey(EncodeProfile, on_delete=models.CASCADE) + + progress = models.PositiveSmallIntegerField(default=0) + + update_date = models.DateTimeField(auto_now=True) + + retries = models.IntegerField(default=0) + + size = models.CharField(max_length=20, blank=True) + + status = models.CharField( + max_length=20, choices=MEDIA_ENCODING_STATUS, default="pending" + ) + + temp_file = models.CharField(max_length=400, blank=True) + + task_id = models.CharField(max_length=100, blank=True) + + total_run_time = models.IntegerField(default=0) + + worker = models.CharField(max_length=100, blank=True) + + @property + def media_encoding_url(self): + if self.media_file: + return helpers.url_from_path(self.media_file.path) + return None + + @property + def media_chunk_url(self): + if self.chunk_file_path: + return helpers.url_from_path(self.chunk_file_path) + return None + + def save(self, *args, **kwargs): + if self.media_file: + cmd = ["stat", "-c", "%s", self.media_file.path] + stdout = helpers.run_command(cmd).get("out") + if stdout: + size = int(stdout.strip()) + self.size = helpers.show_file_size(size) + if self.chunk_file_path and not self.md5sum: + cmd = ["md5sum", self.chunk_file_path] + stdout = helpers.run_command(cmd).get("out") + if stdout: + md5sum = stdout.strip().split()[0] + self.md5sum = md5sum + + super(Encoding, self).save(*args, **kwargs) + + def set_progress(self, progress, commit=True): + if isinstance(progress, int): + if 0 <= progress <= 100: + self.progress = progress + self.save(update_fields=["progress"]) + return True + return False + + def __str__(self): + return "{0}-{1}".format(self.profile.name, self.media.title) + + def get_absolute_url(self): + return reverse("api_get_encoding", kwargs={"encoding_id": self.id}) + + +class Language(models.Model): + """Language model + to be used with Subtitles + """ + + code = models.CharField(max_length=12, help_text="language code") + + title = models.CharField(max_length=100, help_text="language code") + + class Meta: + ordering = ["id"] + + def __str__(self): + return "{0}-{1}".format(self.code, self.title) + + +class Subtitle(models.Model): + """Subtitles model""" + + language = models.ForeignKey(Language, on_delete=models.CASCADE) + + media = models.ForeignKey(Media, on_delete=models.CASCADE, related_name="subtitles") + + subtitle_file = models.FileField( + "Subtitle/CC file", + help_text="File has to be WebVTT format", + upload_to=subtitles_file_path, + max_length=500, + ) + + user = models.ForeignKey("users.User", on_delete=models.CASCADE) + + def __str__(self): + return "{0}-{1}".format(self.media.title, self.language.title) + + +class RatingCategory(models.Model): + """Rating Category + Facilitate user ratings. + One or more rating categories per Category can exist + will be shown to the media if they are enabled + """ + + description = models.TextField(blank=True) + + enabled = models.BooleanField(default=True) + + title = models.CharField(max_length=200, unique=True, db_index=True) + + class Meta: + verbose_name_plural = "Rating Categories" + + def __str__(self): + return "{0}".format(self.title) + + +def validate_rating(value): + if -1 >= value or value > 5: + raise ValidationError("score has to be between 0 and 5") + + +class Rating(models.Model): + """User Rating""" + + add_date = models.DateTimeField(auto_now_add=True) + + media = models.ForeignKey(Media, on_delete=models.CASCADE, related_name="ratings") + + rating_category = models.ForeignKey(RatingCategory, on_delete=models.CASCADE) + + score = models.IntegerField(validators=[validate_rating]) + + user = models.ForeignKey("users.User", on_delete=models.CASCADE) + + class Meta: + verbose_name_plural = "Ratings" + indexes = [ + models.Index(fields=["user", "media"]), + ] + unique_together = ("user", "media", "rating_category") + + def __str__(self): + return "{0}, rate for {1} for category {2}".format( + self.user.username, self.media.title, self.rating_category.title + ) + + +class Playlist(models.Model): + """Playlists model""" + + add_date = models.DateTimeField(auto_now_add=True, db_index=True) + + description = models.TextField(blank=True, help_text="description") + + friendly_token = models.CharField(blank=True, max_length=12, db_index=True) + + media = models.ManyToManyField(Media, through="playlistmedia", blank=True) + + title = models.CharField(max_length=100, db_index=True) + + uid = models.UUIDField(unique=True, default=uuid.uuid4) + + user = models.ForeignKey( + "users.User", on_delete=models.CASCADE, db_index=True, related_name="playlists" + ) + + def __str__(self): + return self.title + + @property + def media_count(self): + return self.media.count() + + def get_absolute_url(self, api=False): + if api: + return reverse( + "api_get_playlist", kwargs={"friendly_token": self.friendly_token} + ) + else: + return reverse( + "get_playlist", kwargs={"friendly_token": self.friendly_token} + ) + + @property + def url(self): + return self.get_absolute_url() + + @property + def api_url(self): + return self.get_absolute_url(api=True) + + def user_thumbnail_url(self): + if self.user.logo: + return helpers.url_from_path(self.user.logo.path) + return None + + def set_ordering(self, media, ordering): + if media not in self.media.all(): + return False + pm = PlaylistMedia.objects.filter(playlist=self, media=media).first() + if pm and isinstance(ordering, int) and 0 < ordering: + pm.ordering = ordering + pm.save() + return True + return False + + def save(self, *args, **kwargs): + strip_text_items = ["title", "description"] + for item in strip_text_items: + setattr(self, item, strip_tags(getattr(self, item, None))) + self.title = self.title[:99] + + if not self.friendly_token: + while True: + friendly_token = helpers.produce_friendly_token() + if not Playlist.objects.filter(friendly_token=friendly_token): + self.friendly_token = friendly_token + break + super(Playlist, self).save(*args, **kwargs) + + @property + def thumbnail_url(self): + pm = self.playlistmedia_set.first() + if pm: + return helpers.url_from_path(pm.media.thumbnail.path) + return None + + +class PlaylistMedia(models.Model): + """Helper model to store playlist specific media""" + + action_date = models.DateTimeField(auto_now=True) + + media = models.ForeignKey(Media, on_delete=models.CASCADE) + + playlist = models.ForeignKey(Playlist, on_delete=models.CASCADE) + + ordering = models.IntegerField(default=1) + + class Meta: + ordering = ["ordering", "-action_date"] + + +class Comment(MPTTModel): + """Comments model""" + + add_date = models.DateTimeField(auto_now_add=True) + + media = models.ForeignKey( + Media, on_delete=models.CASCADE, db_index=True, related_name="comments" + ) + + parent = TreeForeignKey( + "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children" + ) + + text = models.TextField(help_text="text") + + uid = models.UUIDField(unique=True, default=uuid.uuid4) + + user = models.ForeignKey("users.User", on_delete=models.CASCADE, db_index=True) + + class MPTTMeta: + order_insertion_by = ["add_date"] + + def __str__(self): + return "On {0} by {1}".format(self.media.title, self.user.username) + + def save(self, *args, **kwargs): + strip_text_items = ["text"] + for item in strip_text_items: + setattr(self, item, strip_tags(getattr(self, item, None))) + + if self.text: + self.text = self.text[: settings.MAX_CHARS_FOR_COMMENT] + + super(Comment, self).save(*args, **kwargs) + + def get_absolute_url(self): + return reverse("get_media") + "?m={0}".format(self.media.friendly_token) + + @property + def media_url(self): + return self.get_absolute_url() + + +@receiver(post_save, sender=Media) +def media_save(sender, instance, created, **kwargs): + # media_file path is not set correctly until mode is saved + # post_save signal will take care of calling a few functions + # once model is saved + # SOS: do not put anything here, as if more logic is added, + # we have to disconnect signal to avoid infinite recursion + if created: + instance.media_init() + notify_users(friendly_token=instance.friendly_token, action="media_added") + + instance.user.update_user_media() + if instance.category.all(): + # this won't catch when a category + # is removed from a media, which is what we want... + for category in instance.category.all(): + category.update_category_media() + + if instance.tags.all(): + for tag in instance.tags.all(): + tag.update_tag_media() + + instance.update_search_vector() + + +@receiver(pre_delete, sender=Media) +def media_file_pre_delete(sender, instance, **kwargs): + if instance.category.all(): + for category in instance.category.all(): + instance.category.remove(category) + category.update_category_media() + if instance.tags.all(): + for tag in instance.tags.all(): + instance.tags.remove(tag) + tag.update_tag_media() + + +@receiver(post_delete, sender=Media) +def media_file_delete(sender, instance, **kwargs): + """ + Deletes file from filesystem + when corresponding `Media` object is deleted. + """ + + if instance.media_file: + helpers.rm_file(instance.media_file.path) + if instance.thumbnail: + helpers.rm_file(instance.thumbnail.path) + if instance.poster: + helpers.rm_file(instance.poster.path) + if instance.uploaded_thumbnail: + helpers.rm_file(instance.uploaded_thumbnail.path) + if instance.uploaded_poster: + helpers.rm_file(instance.uploaded_poster.path) + if instance.sprites: + helpers.rm_file(instance.sprites.path) + if instance.hls_file: + p = os.path.dirname(instance.hls_file) + helpers.rm_dir(p) + instance.user.update_user_media() + + +@receiver(m2m_changed, sender=Media.category.through) +def media_m2m(sender, instance, **kwargs): + if instance.category.all(): + for category in instance.category.all(): + category.update_category_media() + if instance.tags.all(): + for tag in instance.tags.all(): + tag.update_tag_media() + + +@receiver(post_save, sender=Encoding) +def encoding_file_save(sender, instance, created, **kwargs): + """Performs actions on encoding file delete + For example, if encoding is a chunk file, with encoding_status success, + perform a check if this is the final chunk file of a media, then + concatenate chunks, create final encoding file and delete chunks + """ + + if instance.chunk and instance.status == "success": + # a chunk got completed + + # check if all chunks are OK + # then concatenate to new Encoding - and remove chunks + # this should run only once! + if instance.media_file: + try: + orig_chunks = json.loads(instance.chunks_info).keys() + except BaseException: + instance.delete() + return False + + chunks = Encoding.objects.filter( + media=instance.media, + profile=instance.profile, + chunks_info=instance.chunks_info, + chunk=True, + ).order_by("add_date") + + complete = True + + # perform validation, make sure everything is there + for chunk in orig_chunks: + if not chunks.filter(chunk_file_path=chunk): + complete = False + break + + for chunk in chunks: + if not (chunk.media_file and chunk.media_file.path): + complete = False + break + + if complete: + # concatenate chunks and create final encoding file + chunks_paths = [f.media_file.path for f in chunks] + + with tempfile.TemporaryDirectory( + dir=settings.TEMP_DIRECTORY + ) as temp_dir: + seg_file = helpers.create_temp_file(suffix=".txt", dir=temp_dir) + tf = helpers.create_temp_file( + suffix=".{0}".format(instance.profile.extension), dir=temp_dir + ) + with open(seg_file, "w") as ff: + for f in chunks_paths: + ff.write("file {}\n".format(f)) + cmd = [ + settings.FFMPEG_COMMAND, + "-y", + "-f", + "concat", + "-safe", + "0", + "-i", + seg_file, + "-c", + "copy", + "-pix_fmt", + "yuv420p", + "-movflags", + "faststart", + tf, + ] + stdout = helpers.run_command(cmd) + + encoding = Encoding( + media=instance.media, + profile=instance.profile, + status="success", + progress=100, + ) + all_logs = "\n".join([st.logs for st in chunks]) + encoding.logs = "{0}\n{1}\n{2}".format( + chunks_paths, stdout, all_logs + ) + workers = list(set([st.worker for st in chunks])) + encoding.worker = json.dumps({"workers": workers}) + + start_date = min([st.add_date for st in chunks]) + end_date = max([st.update_date for st in chunks]) + encoding.total_run_time = (end_date - start_date).seconds + encoding.save() + + with open(tf, "rb") as f: + myfile = File(f) + output_name = "{0}.{1}".format( + helpers.get_file_name(instance.media.media_file.path), + instance.profile.extension, + ) + encoding.media_file.save(content=myfile, name=output_name) + + # encoding is saved, deleting chunks + # and any other encoding that might exist + # first perform one last validation + # to avoid that this is run twice + if ( + len(orig_chunks) + == Encoding.objects.filter( + media=instance.media, + profile=instance.profile, + chunks_info=instance.chunks_info, + ).count() + ): + # if two chunks are finished at the same time, this + # will be changed + who = Encoding.objects.filter( + media=encoding.media, profile=encoding.profile + ).exclude(id=encoding.id) + who.delete() + else: + encoding.delete() + if not Encoding.objects.filter(chunks_info=instance.chunks_info): + # TODO: in case of remote workers, files should be deleted + # example + # for worker in workers: + # for chunk in json.loads(instance.chunks_info).keys(): + # remove_media_file.delay(media_file=chunk) + for chunk in json.loads(instance.chunks_info).keys(): + helpers.rm_file(chunk) + instance.media.post_encode_actions(encoding=instance, action="add") + + elif instance.chunk and instance.status == "fail": + encoding = Encoding( + media=instance.media, profile=instance.profile, status="fail", progress=100 + ) + + chunks = Encoding.objects.filter( + media=instance.media, chunks_info=instance.chunks_info, chunk=True + ).order_by("add_date") + + chunks_paths = [f.media_file.path for f in chunks] + + all_logs = "\n".join([st.logs for st in chunks]) + encoding.logs = "{0}\n{1}\n{2}".format(chunks_paths, all_logs) + workers = list(set([st.worker for st in chunks])) + encoding.worker = json.dumps({"workers": workers}) + start_date = min([st.add_date for st in chunks]) + end_date = max([st.update_date for st in chunks]) + encoding.total_run_time = (end_date - start_date).seconds + encoding.save() + + who = Encoding.objects.filter( + media=encoding.media, profile=encoding.profile + ).exclude(id=encoding.id) + + who.delete() + pass # TODO: merge with above if, do not repeat code + else: + if instance.status in ["fail", "success"]: + instance.media.post_encode_actions(encoding=instance, action="add") + + encodings = set( + [ + encoding.status + for encoding in Encoding.objects.filter(media=instance.media) + ] + ) + if ("running" in encodings) or ("pending" in encodings): + return + workers = list( + set( + [ + encoding.worker + for encoding in Encoding.objects.filter(media=instance.media) + ] + ) + ) + + +@receiver(post_delete, sender=Encoding) +def encoding_file_delete(sender, instance, **kwargs): + """ + Deletes file from filesystem + when corresponding `Encoding` object is deleted. + """ + + if instance.media_file: + helpers.rm_file(instance.media_file.path) + if not instance.chunk: + instance.media.post_encode_actions(encoding=instance, action="delete") + # delete local chunks, and remote chunks + media file. Only when the + # last encoding of a media is complete diff --git a/files/permissions.py b/files/permissions.py new file mode 100644 index 0000000..5601166 --- /dev/null +++ b/files/permissions.py @@ -0,0 +1,9 @@ +from rest_framework import permissions +from .methods import is_mediacms_editor + + +class IsMediacmsEditor(permissions.BasePermission): + def has_permission(self, request, view): + if is_mediacms_editor(request.user): + return True + return False diff --git a/files/serializers.py b/files/serializers.py new file mode 100644 index 0000000..75afc14 --- /dev/null +++ b/files/serializers.py @@ -0,0 +1,257 @@ +from rest_framework import serializers + +from .models import Media, EncodeProfile, Playlist, Comment, Category, Tag + +# TODO: put them in a more DRY way + + +class MediaSerializer(serializers.ModelSerializer): + # to be used in APIs as show related media + user = serializers.ReadOnlyField(source="user.username") + url = serializers.SerializerMethodField() + api_url = serializers.SerializerMethodField() + thumbnail_url = serializers.SerializerMethodField() + author_profile = serializers.SerializerMethodField() + author_thumbnail = serializers.SerializerMethodField() + + def get_url(self, obj): + return self.context["request"].build_absolute_uri(obj.get_absolute_url()) + + def get_api_url(self, obj): + return self.context["request"].build_absolute_uri( + obj.get_absolute_url(api=True) + ) + + def get_thumbnail_url(self, obj): + return self.context["request"].build_absolute_uri(obj.thumbnail_url) + + def get_author_profile(self, obj): + return self.context["request"].build_absolute_uri(obj.author_profile()) + + def get_author_thumbnail(self, obj): + return self.context["request"].build_absolute_uri(obj.author_thumbnail()) + + class Meta: + model = Media + read_only_fields = ( + "friendly_token", + "user", + "add_date", + "views", + "media_type", + "state", + "duration", + "encoding_status", + "views", + "likes", + "dislikes", + "reported_times", + "size", + "is_reviewed", + ) + fields = ( + "friendly_token", + "url", + "api_url", + "user", + "title", + "description", + "add_date", + "views", + "media_type", + "state", + "duration", + "thumbnail_url", + "is_reviewed", + "url", + "api_url", + "preview_url", + "author_name", + "author_profile", + "author_thumbnail", + "encoding_status", + "views", + "likes", + "dislikes", + "reported_times", + "featured", + "user_featured", + "size", + ) + + +class SingleMediaSerializer(serializers.ModelSerializer): + user = serializers.ReadOnlyField(source="user.username") + url = serializers.SerializerMethodField() + + def get_url(self, obj): + return self.context["request"].build_absolute_uri(obj.get_absolute_url()) + + class Meta: + model = Media + read_only_fields = ( + "friendly_token", + "user", + "add_date", + "views", + "media_type", + "state", + "duration", + "encoding_status", + "views", + "likes", + "dislikes", + "reported_times", + "size", + "video_height", + "is_reviewed", + ) + fields = ( + "url", + "user", + "title", + "description", + "add_date", + "edit_date", + "media_type", + "state", + "duration", + "thumbnail_url", + "poster_url", + "thumbnail_time", + "url", + "sprites_url", + "preview_url", + "author_name", + "author_profile", + "author_thumbnail", + "encodings_info", + "encoding_status", + "views", + "likes", + "dislikes", + "reported_times", + "user_featured", + "original_media_url", + "size", + "video_height", + "enable_comments", + "categories_info", + "is_reviewed", + "edit_url", + "tags_info", + "hls_info", + "license", + "subtitles_info", + "ratings_info", + "add_subtitle_url", + "allow_download", + ) + + +class MediaSearchSerializer(serializers.ModelSerializer): + url = serializers.SerializerMethodField() + + def get_url(self, obj): + return self.context["request"].build_absolute_uri(obj.get_absolute_url()) + + class Meta: + model = Media + fields = ( + "title", + "author_name", + "author_profile", + "thumbnail_url", + "add_date", + "views", + "description", + "friendly_token", + "duration", + "url", + "media_type", + "preview_url", + "categories_info", + ) + + +class EncodeProfileSerializer(serializers.ModelSerializer): + class Meta: + model = EncodeProfile + fields = ("name", "extension", "resolution", "codec", "description") + + +class CategorySerializer(serializers.ModelSerializer): + user = serializers.ReadOnlyField(source="user.username") + + class Meta: + model = Category + fields = ( + "title", + "description", + "is_global", + "media_count", + "user", + "thumbnail_url", + ) + + +class TagSerializer(serializers.ModelSerializer): + class Meta: + model = Tag + fields = ("title", "media_count", "thumbnail_url") + + +class PlaylistSerializer(serializers.ModelSerializer): + user = serializers.ReadOnlyField(source="user.username") + + class Meta: + model = Playlist + read_only_fields = ("add_date", "user") + fields = ( + "add_date", + "title", + "description", + "user", + "media_count", + "url", + "api_url", + "thumbnail_url" + ) + + +class PlaylistDetailSerializer(serializers.ModelSerializer): + user = serializers.ReadOnlyField(source="user.username") + + class Meta: + model = Playlist + read_only_fields = ("add_date", "user") + fields = ( + "title", + "add_date", + "user_thumbnail_url", + "description", + "user", + "media_count", + "url", + "thumbnail_url" + ) + + +class CommentSerializer(serializers.ModelSerializer): + author_profile = serializers.ReadOnlyField(source="user.get_absolute_url") + author_name = serializers.ReadOnlyField(source="user.name") + author_thumbnail_url = serializers.ReadOnlyField(source="user.thumbnail_url") + + class Meta: + model = Comment + read_only_fields = ("add_date", "uid") + fields = ( + "add_date", + "text", + "parent", + "author_thumbnail_url", + "author_profile", + "author_name", + "media_url", + "uid", + ) diff --git a/files/stop_words.py b/files/stop_words.py new file mode 100644 index 0000000..021685f --- /dev/null +++ b/files/stop_words.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + + +STOP_WORDS = set( + """ +a about above across after afterwards again against all almost alone along +already also although always am among amongst amount an and another any anyhow +anyone anything anyway anywhere are around as at +back be became because become becomes becoming been before beforehand behind +being below beside besides between beyond both bottom but by +call can cannot ca could +did do does doing done down due during +each eight either eleven else elsewhere empty enough even ever every +everyone everything everywhere except +few fifteen fifty first five for former formerly forty four from front full +further +get give go +had has have he hence her here hereafter hereby herein hereupon hers herself +him himself his how however hundred +i if in indeed into is it its itself +keep +last latter latterly least less +just +made make many may me meanwhile might mine more moreover most mostly move much +must my myself +name namely neither never nevertheless next nine no nobody none noone nor not +nothing now nowhere +of off often on once one only onto or other others otherwise our ours ourselves +out over own +part per perhaps please put +quite +rather re really regarding +same say see seem seemed seeming seems serious several she should show side +since six sixty so some somehow someone something sometime sometimes somewhere +still such +take ten than that the their them themselves then thence there thereafter +thereby therefore therein thereupon these they third this those though three +through throughout thru thus to together too top toward towards twelve twenty +two +under until up unless upon us used using +various very very via was we well were what whatever when whence whenever where +whereafter whereas whereby wherein whereupon wherever whether which while +whither who whoever whole whom whose why will with within without would +yet you your yours yourself yourselves +""".split() +) + +SPANISH_STOP_WORDS = set( + """ +a actualmente acuerdo adelante ademas además adrede afirmó agregó ahi ahora ahí al algo alguna algunas alguno algunos algún alli allí alrededor ambos ampleamos antano antaño ante anterior antes apenas aproximadamente aquel aquella aquellas aquello aquellos aqui aquél aquélla aquéllas aquéllos aquí arriba arribaabajo aseguró asi así atras aun aunque ayer añadió aún +b bajo bastante bien breve buen buena buenas bueno buenos +c cada casi cerca cierta ciertas cierto ciertos cinco claro comentó como con conmigo conocer conseguimos conseguir considera consideró consigo consigue consiguen consigues contigo contra cosas creo cual cuales cualquier cuando cuanta cuantas cuanto cuantos cuatro cuenta cuál cuáles cuándo cuánta cuántas cuánto cuántos cómo +d da dado dan dar de debajo debe deben debido decir dejó del delante demasiado demás dentro deprisa desde despacio despues después detras detrás dia dias dice dicen dicho dieron diferente diferentes dijeron dijo dio donde dos durante día días dónde +e ejemplo el ella ellas ello ellos embargo empleais emplean emplear empleas empleo en encima encuentra enfrente enseguida entonces entre era erais eramos eran eras eres es esa esas ese eso esos esta estaba estabais estaban estabas estad estada estadas estado estados estais estamos estan estando estar estaremos estará estarán estarás estaré estaréis estaría estaríais estaríamos estarían estarías estas este estemos esto estos estoy estuve estuviera estuvierais estuvieran estuvieras estuvieron estuviese estuvieseis estuviesen estuvieses estuvimos estuviste estuvisteis estuviéramos estuviésemos estuvo está estábamos estáis están estás esté estéis estén estés ex excepto existe existen explicó expresó +f fin final fue fuera fuerais fueran fueras fueron fuese fueseis fuesen fueses fui fuimos fuiste fuisteis fuéramos fuésemos +g general gran grandes gueno +h ha haber habia habida habidas habido habidos habiendo habla hablan habremos habrá habrán habrás habré habréis habría habríais habríamos habrían habrías habéis había habíais habíamos habían habías hace haceis hacemos hacen hacer hacerlo haces hacia haciendo hago han has hasta hay haya hayamos hayan hayas hayáis he hecho hemos hicieron hizo horas hoy hube hubiera hubierais hubieran hubieras hubieron hubiese hubieseis hubiesen hubieses hubimos hubiste hubisteis hubiéramos hubiésemos hubo +i igual incluso indicó informo informó intenta intentais intentamos intentan intentar intentas intento ir +j junto +k +l la lado largo las le lejos les llegó lleva llevar lo los luego lugar +m mal manera manifestó mas mayor me mediante medio mejor mencionó menos menudo mi mia mias mientras mio mios mis misma mismas mismo mismos modo momento mucha muchas mucho muchos muy más mí mía mías mío míos +n nada nadie ni ninguna ningunas ninguno ningunos ningún no nos nosotras nosotros nuestra nuestras nuestro nuestros nueva nuevas nuevo nuevos nunca +o ocho os otra otras otro otros +p pais para parece parte partir pasada pasado paìs peor pero pesar poca pocas poco pocos podeis podemos poder podria podriais podriamos podrian podrias podrá podrán podría podrían poner por por qué porque posible primer primera primero primeros principalmente pronto propia propias propio propios proximo próximo próximos pudo pueda puede pueden puedo pues +q qeu que quedó queremos quien quienes quiere quiza quizas quizá quizás quién quiénes qué +r raras realizado realizar realizó repente respecto +s sabe sabeis sabemos saben saber sabes sal salvo se sea seamos sean seas segun segunda segundo según seis ser sera seremos será serán serás seré seréis sería seríais seríamos serían serías seáis señaló si sido siempre siendo siete sigue siguiente sin sino sobre sois sola solamente solas solo solos somos son soy soyos su supuesto sus suya suyas suyo suyos sé sí sólo +t tal tambien también tampoco tan tanto tarde te temprano tendremos tendrá tendrán tendrás tendré tendréis tendría tendríais tendríamos tendrían tendrías tened teneis tenemos tener tenga tengamos tengan tengas tengo tengáis tenida tenidas tenido tenidos teniendo tenéis tenía teníais teníamos tenían tenías tercera ti tiempo tiene tienen tienes toda todas todavia todavía todo todos total trabaja trabajais trabajamos trabajan trabajar trabajas trabajo tras trata través tres tu tus tuve tuviera tuvierais tuvieran tuvieras tuvieron tuviese tuvieseis tuviesen tuvieses tuvimos tuviste tuvisteis tuviéramos tuviésemos tuvo tuya tuyas tuyo tuyos tú +u ultimo un una unas uno unos usa usais usamos usan usar usas uso usted ustedes +v va vais valor vamos van varias varios vaya veces ver verdad verdadera verdadero vez vosotras vosotros voy vuestra vuestras vuestro vuestros +w +x +y ya yo +z +él éramos ésa ésas ése ésos ésta éstas éste éstos última últimas último últimos +""".split() +) + +STOP_WORDS.update(SPANISH_STOP_WORDS) +contractions = ["n't", "'d", "'ll", "'m", "'re", "'s", "'ve"] +STOP_WORDS.update(contractions) + +for apostrophe in ["‘", "’"]: + for stopword in contractions: + STOP_WORDS.add(stopword.replace("'", apostrophe)) diff --git a/files/tasks.py b/files/tasks.py new file mode 100644 index 0000000..cf7125f --- /dev/null +++ b/files/tasks.py @@ -0,0 +1,851 @@ +import re +import os +import json +import subprocess +from datetime import datetime, timedelta +import tempfile +import shutil +from django.core.cache import cache +from django.conf import settings +from django.core.files import File +from django.db.models import Q + +from celery import Task +from celery.decorators import task +from celery.utils.log import get_task_logger +from celery.exceptions import SoftTimeLimitExceeded + +from celery.task.control import revoke +from celery.signals import task_revoked + +from .backends import FFmpegBackend +from .exceptions import VideoEncodingError +from .helpers import ( + calculate_seconds, + rm_file, + create_temp_file, + get_file_name, + get_file_type, + media_file_info, + run_command, + produce_ffmpeg_commands, + produce_friendly_token, +) + +from actions.models import MediaAction, USER_MEDIA_ACTIONS +from users.models import User +from .models import Encoding, EncodeProfile, Media, Category, Rating, Tag +from .methods import list_tasks, pre_save_action, notify_users + +logger = get_task_logger(__name__) + +VALID_USER_ACTIONS = [action for action, name in USER_MEDIA_ACTIONS] + +ERRORS_LIST = [ + "Output file is empty, nothing was encoded", + "Invalid data found when processing input", + "Unable to find a suitable output format for", +] + + +@task(name="chunkize_media", bind=True, queue="short_tasks", soft_time_limit=60 * 30) +def chunkize_media(self, friendly_token, profiles, force=True): + """Break media in chunks and start encoding tasks""" + + profiles = [EncodeProfile.objects.get(id=profile) for profile in profiles] + media = Media.objects.get(friendly_token=friendly_token) + cwd = os.path.dirname(os.path.realpath(media.media_file.path)) + file_name = media.media_file.path.split("/")[-1] + random_prefix = produce_friendly_token() + file_format = "{0}_{1}".format(random_prefix, file_name) + chunks_file_name = "%02d_{0}".format(file_format) + chunks_file_name += ".mkv" + cmd = [ + settings.FFMPEG_COMMAND, + "-y", + "-i", + media.media_file.path, + "-c", + "copy", + "-f", + "segment", + "-segment_time", + str(settings.VIDEO_CHUNKS_DURATION), + chunks_file_name, + ] + chunks = [] + ret = run_command(cmd, cwd=cwd) + + if "out" in ret.keys(): + for line in ret.get("error").split("\n"): + ch = re.findall(r"Opening \'([\W\w]+)\' for writing", line) + if ch: + chunks.append(ch[0]) + if not chunks: + # command completely failed to segment file.putting to normal encode + logger.info( + "Failed to break file {0} in chunks." + " Putting to normal encode queue".format(friendly_token) + ) + for profile in profiles: + if media.video_height and media.video_height < profile.resolution: + if profile.resolution not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE: + continue + encoding = Encoding(media=media, profile=profile) + encoding.save() + enc_url = settings.SSL_FRONTEND_HOST + encoding.get_absolute_url() + encode_media.delay( + friendly_token, profile.id, encoding.id, enc_url, force=force + ) + return False + + chunks = [os.path.join(cwd, ch) for ch in chunks] + to_profiles = [] + chunks_dict = {} + # calculate once md5sums + for chunk in chunks: + cmd = ["md5sum", chunk] + stdout = run_command(cmd).get("out") + md5sum = stdout.strip().split()[0] + chunks_dict[chunk] = md5sum + + for profile in profiles: + if media.video_height and media.video_height < profile.resolution: + if profile.resolution not in settings.MINIMUM_RESOLUTIONS_TO_ENCODE: + continue + to_profiles.append(profile) + + for chunk in chunks: + encoding = Encoding( + media=media, + profile=profile, + chunk_file_path=chunk, + chunk=True, + chunks_info=json.dumps(chunks_dict), + md5sum=chunks_dict[chunk], + ) + + encoding.save() + enc_url = settings.SSL_FRONTEND_HOST + encoding.get_absolute_url() + if profile.resolution in settings.MINIMUM_RESOLUTIONS_TO_ENCODE: + priority = 0 + else: + priority = 9 + encode_media.apply_async( + args=[friendly_token, profile.id, encoding.id, enc_url], + kwargs={"force": force, "chunk": True, "chunk_file_path": chunk}, + priority=priority, + ) + + logger.info( + "got {0} chunks and will encode to {1} profiles".format( + len(chunks), to_profiles + ) + ) + return True + + +class EncodingTask(Task): + def on_failure(self, exc, task_id, args, kwargs, einfo): + # mainly used to run some post failure steps + # we get here if a task is revoked + try: + if hasattr(self, "encoding"): + self.encoding.status = "fail" + self.encoding.save(update_fields=["status"]) + kill_ffmpeg_process(self.encoding.temp_file) + if hasattr(self.encoding, "media"): + self.encoding.media.post_encode_actions() + except BaseException: + pass + return False + + +@task( + name="encode_media", + base=EncodingTask, + bind=True, + queue="long_tasks", + soft_time_limit=settings.CELERY_SOFT_TIME_LIMIT, +) +def encode_media( + self, + friendly_token, + profile_id, + encoding_id, + encoding_url, + force=True, + chunk=False, + chunk_file_path="", +): + """Encode a media to given profile, using ffmpeg, storing progress""" + + logger.info( + "Encode Media started, friendly token {0}, profile id {1}, force {2}".format( + friendly_token, profile_id, force + ) + ) + + if self.request.id: + task_id = self.request.id + else: + task_id = None + try: + media = Media.objects.get(friendly_token=friendly_token) + profile = EncodeProfile.objects.get(id=profile_id) + except BaseException: + Encoding.objects.filter(id=encoding_id).delete() + return False + + # break logic with chunk True/False + if chunk: + # TODO: in case a video is chunkized and this enters here many times + # it will always run since chunk_file_path is always different + # thus find a better way for this check + if ( + Encoding.objects.filter( + media=media, profile=profile, chunk_file_path=chunk_file_path + ).count() + > 1 + and force == False + ): + Encoding.objects.filter(id=encoding_id).delete() + return False + else: + try: + encoding = Encoding.objects.get(id=encoding_id) + encoding.status = "running" + Encoding.objects.filter( + media=media, + profile=profile, + chunk=True, + chunk_file_path=chunk_file_path, + ).exclude(id=encoding_id).delete() + except BaseException: + encoding = Encoding( + media=media, + profile=profile, + status="running", + chunk=True, + chunk_file_path=chunk_file_path, + ) + else: + if ( + Encoding.objects.filter(media=media, profile=profile).count() > 1 + and force is False + ): + Encoding.objects.filter(id=encoding_id).delete() + return False + else: + try: + encoding = Encoding.objects.get(id=encoding_id) + encoding.status = "running" + Encoding.objects.filter(media=media, profile=profile).exclude( + id=encoding_id + ).delete() + except BaseException: + encoding = Encoding(media=media, profile=profile, status="running") + + if task_id: + encoding.task_id = task_id + encoding.worker = "localhost" + encoding.retries = self.request.retries + encoding.save() + + if profile.extension == "gif": + tf = create_temp_file(suffix=".gif") + # -ss 5 start from 5 second. -t 25 until 25 sec + command = [ + settings.FFMPEG_COMMAND, + "-y", + "-ss", + "3", + "-i", + media.media_file.path, + "-hide_banner", + "-vf", + "scale=344:-1:flags=lanczos,fps=1", + "-t", + "25", + "-f", + "gif", + tf, + ] + ret = run_command(command) + if os.path.exists(tf) and get_file_type(tf) == "image": + with open(tf, "rb") as f: + myfile = File(f) + encoding.status = "success" + encoding.media_file.save(content=myfile, name=tf) + rm_file(tf) + return True + else: + return False + + if chunk: + original_media_path = chunk_file_path + else: + original_media_path = media.media_file.path + + if not media.duration: + encoding.status = "fail" + encoding.save(update_fields=["status"]) + return False + + with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as temp_dir: + + tf = create_temp_file(suffix=".{0}".format(profile.extension), dir=temp_dir) + tfpass = create_temp_file(suffix=".{0}".format(profile.extension), dir=temp_dir) + ffmpeg_commands = produce_ffmpeg_commands( + original_media_path, + media.media_info, + resolution=profile.resolution, + codec=profile.codec, + output_filename=tf, + pass_file=tfpass, + chunk=chunk, + ) + if not ffmpeg_commands: + encoding.status = "fail" + encoding.save(update_fields=["status"]) + return False + + encoding.temp_file = tf + encoding.commands = str(ffmpeg_commands) + + encoding.save(update_fields=["temp_file", "commands", "task_id"]) + + # binding these, so they are available on on_failure + self.encoding = encoding + self.media = media + # can be one-pass or two-pass + for ffmpeg_command in ffmpeg_commands: + ffmpeg_command = [str(s) for s in ffmpeg_command] + encoding_backend = FFmpegBackend() + try: + encoding_command = encoding_backend.encode(ffmpeg_command) + duration, n_times = 0, 0 + output = "" + while encoding_command: + try: + # TODO: understand an eternal loop + # eg h265 with mv4 file issue, and stop with error + output = next(encoding_command) + duration = calculate_seconds(output) + if duration: + percent = duration * 100 / media.duration + if n_times % 60 == 0: + encoding.progress = percent + try: + encoding.save( + update_fields=["progress", "update_date"] + ) + logger.info("Saved {0}".format(round(percent, 2))) + except BaseException: + pass + n_times += 1 + except StopIteration: + break + except VideoEncodingError: + # ffmpeg error, or ffmpeg was killed + raise + except Exception as e: + try: + # output is empty, fail message is on the exception + output = e.message + except AttributeError: + output = "" + if isinstance(e, SoftTimeLimitExceeded): + kill_ffmpeg_process(encoding.temp_file) + encoding.logs = output + encoding.status = "fail" + encoding.save(update_fields=["status", "logs"]) + raise_exception = True + # if this is an ffmpeg's valid error + # no need for the task to be re-run + # otherwise rerun task... + for error_msg in ERRORS_LIST: + if error_msg.lower() in output.lower(): + raise_exception = False + if raise_exception: + raise self.retry(exc=e, countdown=5, max_retries=1) + + encoding.logs = output + encoding.progress = 100 + + success = False + encoding.status = "fail" + if os.path.exists(tf) and os.path.getsize(tf) != 0: + ret = media_file_info(tf) + if ret.get("is_video") or ret.get("is_audio"): + encoding.status = "success" + success = True + + with open(tf, "rb") as f: + myfile = File(f) + output_name = "{0}.{1}".format( + get_file_name(original_media_path), profile.extension + ) + encoding.media_file.save(content=myfile, name=output_name) + encoding.total_run_time = ( + encoding.update_date - encoding.add_date + ).seconds + + try: + encoding.save( + update_fields=["status", "logs", "progress", "total_run_time"] + ) + # this will raise a django.db.utils.DatabaseError error when task is revoked, + # since we delete the encoding at that stage + except BaseException: + pass + + return success + + +@task(name="produce_sprite_from_video", queue="long_tasks") +def produce_sprite_from_video(friendly_token): + """Produces a sprites file for a video, uses ffmpeg""" + + try: + media = Media.objects.get(friendly_token=friendly_token) + except BaseException: + logger.info("failed to get media with friendly_token %s" % friendly_token) + return False + + with tempfile.TemporaryDirectory(dir=settings.TEMP_DIRECTORY) as tmpdirname: + try: + tmpdir_image_files = tmpdirname + "/img%03d.jpg" + output_name = tmpdirname + "/sprites.jpg" + cmd = "{0} -i {1} -f image2 -vf 'fps=1/10, scale=160:90' {2}&&files=$(ls {3}/img*.jpg | sort -t '-' -n -k 2 | tr '\n' ' ')&&convert $files -append {4}".format( + settings.FFMPEG_COMMAND, + media.media_file.path, + tmpdir_image_files, + tmpdirname, + output_name, + ) + ret = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) + if os.path.exists(output_name) and get_file_type(output_name) == "image": + with open(output_name, "rb") as f: + myfile = File(f) + media.sprites.save( + content=myfile, + name=get_file_name(media.media_file.path) + "sprites.jpg", + ) + except BaseException: + pass + return True + + +@task(name="create_hls", queue="long_tasks") +def create_hls(friendly_token): + """Creates HLS file for media, uses Bento4 mp4hls command""" + + if not hasattr(settings, "MP4HLS_COMMAND"): + logger.info("Bento4 mp4hls command is missing from configuration") + return False + + if not os.path.exists(settings.MP4HLS_COMMAND): + logger.info("Bento4 mp4hls command is missing") + return False + + try: + media = Media.objects.get(friendly_token=friendly_token) + except BaseException: + logger.info("failed to get media with friendly_token %s" % friendly_token) + return False + + p = media.uid.hex + output_dir = os.path.join(settings.HLS_DIR, p) + encodings = media.encodings.filter( + profile__extension="mp4", status="success", chunk=False, profile__codec="h264" + ) + if encodings: + existing_output_dir = None + if os.path.exists(output_dir): + existing_output_dir = output_dir + output_dir = os.path.join(settings.HLS_DIR, p + produce_friendly_token()) + files = " ".join([f.media_file.path for f in encodings if f.media_file]) + cmd = "{0} --segment-duration=4 --output-dir={1} {2}".format( + settings.MP4HLS_COMMAND, output_dir, files + ) + ret = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) + if existing_output_dir: + # override content with -T ! + cmd = "cp -rT {0} {1}".format(output_dir, existing_output_dir) + ret = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) + shutil.rmtree(output_dir) + output_dir = existing_output_dir + pp = os.path.join(output_dir, "master.m3u8") + if os.path.exists(pp): + if media.hls_file != pp: + media.hls_file = pp + media.save(update_fields=["hls_file"]) + return True + + +@task(name="check_running_states", queue="short_tasks") +def check_running_states(): + # Experimental - unused + """Check stale running encodings and delete/reencode media""" + + encodings = Encoding.objects.filter(status="running") + + logger.info("got {0} encodings that are in state running".format(encodings.count())) + changed = 0 + for encoding in encodings: + now = datetime.now(encoding.update_date.tzinfo) + if (now - encoding.update_date).seconds > settings.RUNNING_STATE_STALE: + media = encoding.media + profile = encoding.profile + task_id = encoding.task_id + # terminate task + if task_id: + revoke(task_id, terminate=True) + encoding.delete() + media.encode(profiles=[profile]) + # TODO: allign with new code + chunksize... + changed += 1 + if changed: + logger.info("changed from running to pending on {0} items".format(changed)) + return True + + +@task(name="check_media_states", queue="short_tasks") +def check_media_states(): + # Experimental - unused + # check encoding status of not success media + media = Media.objects.filter( + Q(encoding_status="running") + | Q(encoding_status="fail") + | Q(encoding_status="pending") + ) + + logger.info("got {0} media that are not in state success".format(media.count())) + + changed = 0 + for m in media: + m.set_encoding_status() + m.save(update_fields=["encoding_status"]) + changed += 1 + if changed: + logger.info("changed encoding status to {0} media items".format(changed)) + return True + + +@task(name="check_pending_states", queue="short_tasks") +def check_pending_states(): + # Experimental - unused + # check encoding profiles that are on state pending and not on a queue + encodings = Encoding.objects.filter(status="pending") + + if not encodings: + return True + + changed = 0 + tasks = list_tasks() + task_ids = tasks["task_ids"] + media_profile_pairs = tasks["media_profile_pairs"] + for encoding in encodings: + if encoding.task_id and encoding.task_id in task_ids: + # encoding is in one of the active/reserved/scheduled tasks list + continue + elif ( + encoding.media.friendly_token, + encoding.profile.id, + ) in media_profile_pairs: + continue + # encoding is in one of the reserved/scheduled tasks list. + # has no task_id but will be run, so need to re-enter the queue + else: + media = encoding.media + profile = encoding.profile + encoding.delete() + media.encode(profiles=[profile], force=False) + changed += 1 + if changed: + logger.info( + "set to the encode queue {0} encodings that were on pending state".format( + changed + ) + ) + return True + + +@task(name="check_missing_profiles", queue="short_tasks") +def check_missing_profiles(): + # Experimental - unused + + # check if video files have missing profiles. If so, add them + media = Media.objects.filter(media_type="video") + profiles = list(EncodeProfile.objects.all()) + + changed = 0 + + for m in media: + existing_profiles = [p.profile for p in m.encodings.all()] + missing_profiles = [p for p in profiles if p not in existing_profiles] + if missing_profiles: + m.encode(profiles=missing_profiles, force=False) + # since we call with force=False + # encode_media won't delete existing profiles + # if they appear on the meanwhile (eg on a big queue) + changed += 1 + if changed: + logger.info("set to the encode queue {0} profiles".format(changed)) + return True + + +@task(name="clear_sessions", queue="short_tasks") +def clear_sessions(): + """Clear expired sessions""" + + try: + from importlib import import_module + from django.conf import settings + + engine = import_module(settings.SESSION_ENGINE) + engine.SessionStore.clear_expired() + except BaseException: + return False + return True + + +@task(name="save_user_action", queue="short_tasks") +def save_user_action( + user_or_session, friendly_token=None, action="watch", extra_info=None +): + """Short task that saves a user action""" + + if action not in VALID_USER_ACTIONS: + return False + + try: + media = Media.objects.get(friendly_token=friendly_token) + except BaseException: + return False + + user = user_or_session.get("user_id") + session_key = user_or_session.get("user_session") + remote_ip = user_or_session.get("remote_ip_addr") + + if user: + try: + user = User.objects.get(id=user) + except BaseException: + return False + + if not (user or session_key): + return False + + if action in ["like", "dislike", "report"]: + if not pre_save_action( + media=media, + user=user, + session_key=session_key, + action=action, + remote_ip=remote_ip, + ): + return False + + if action == "watch": + if user: + MediaAction.objects.filter(user=user, media=media, action="watch").delete() + else: + MediaAction.objects.filter( + session_key=session_key, media=media, action="watch" + ).delete() + if action == "rate": + try: + score = extra_info.get("score") + rating_category = extra_info.get("category_id") + except BaseException: + # TODO: better error handling? + return False + try: + rating = Rating.objects.filter( + user=user, media=media, rating_category_id=rating_category + ).first() + if rating: + rating.score = score + rating.save(update_fields=["score"]) + else: + rating = Rating.objects.create( + user=user, + media=media, + rating_category_id=rating_category, + score=score, + ) + except Exception as exc: + # TODO: more specific handling, for errors in score, or + # rating_category? + return False + + ma = MediaAction( + user=user, + session_key=session_key, + media=media, + action=action, + extra_info=extra_info, + remote_ip=remote_ip, + ) + ma.save() + + if action == "watch": + media.views += 1 + media.save(update_fields=["views"]) + elif action == "report": + media.reported_times += 1 + + if media.reported_times >= settings.REPORTED_TIMES_THRESHOLD: + media.state = "private" + media.save(update_fields=["reported_times", "state"]) + + notify_users( + friendly_token=media.friendly_token, + action="media_reported", + extra=extra_info, + ) + elif action == "like": + media.likes += 1 + media.save(update_fields=["likes"]) + elif action == "dislike": + media.dislikes += 1 + media.save(update_fields=["dislikes"]) + + return True + + +@task(name="get_list_of_popular_media", queue="long_tasks") +def get_list_of_popular_media(): + """Experimental task for preparing media listing + for index page / recommended section + calculate and return the top 50 popular media, based on two rules + X = the top 25 videos that have the most views during the last week + Y = the most recent 25 videos that have been liked over the last 6 months + """ + + valid_media_x = {} + valid_media_y = {} + basic_query = Q(listable=True) + media_x = Media.objects.filter(basic_query).values("friendly_token") + + period_x = datetime.now() - timedelta(days=7) + period_y = datetime.now() - timedelta(days=30 * 6) + + for media in media_x: + ft = media["friendly_token"] + num = MediaAction.objects.filter( + action_date__gte=period_x, action="watch", media__friendly_token=ft + ).count() + if num: + valid_media_x[ft] = num + num = MediaAction.objects.filter( + action_date__gte=period_y, action="like", media__friendly_token=ft + ).count() + if num: + valid_media_y[ft] = num + + x = sorted(valid_media_x.items(), key=lambda kv: kv[1], reverse=True)[:25] + y = sorted(valid_media_y.items(), key=lambda kv: kv[1], reverse=True)[:25] + + media_ids = [a[0] for a in x] + media_ids.extend([a[0] for a in y]) + media_ids = list(set(media_ids)) + cache.set("popular_media_ids", media_ids, 60 * 60 * 12) + logger.info("saved popular media ids") + + return True + + +@task(name="update_listings_thumbnails", queue="long_tasks") +def update_listings_thumbnails(): + """Populate listings_thumbnail field for models""" + + # Categories + used_media = [] + saved = 0 + qs = Category.objects.filter().order_by("-media_count") + for object in qs: + media = ( + Media.objects.exclude(friendly_token__in=used_media) + .filter(category=object, state="public", is_reviewed=True) + .order_by("-views") + .first() + ) + if media: + object.listings_thumbnail = media.thumbnail_url + object.save(update_fields=["listings_thumbnail"]) + used_media.append(media.friendly_token) + saved += 1 + logger.info("updated {} categories".format(saved)) + + # Tags + used_media = [] + saved = 0 + qs = Tag.objects.filter().order_by("-media_count") + for object in qs: + media = ( + Media.objects.exclude(friendly_token__in=used_media) + .filter(tags=object, state="public", is_reviewed=True) + .order_by("-views") + .first() + ) + if media: + object.listings_thumbnail = media.thumbnail_url + object.save(update_fields=["listings_thumbnail"]) + used_media.append(media.friendly_token) + saved += 1 + logger.info("updated {} tags".format(saved)) + + return True + + +@task_revoked.connect +def task_sent_handler(sender=None, headers=None, body=None, **kwargs): + # For encode_media tasks that are revoked, + # ffmpeg command won't be stopped, since + # it got started by a subprocess. + # Need to stop that process + # Also, removing the Encoding object, + # since the task that would prepare it was killed + # Maybe add a killed state for Encoding objects + try: + uid = kwargs["request"].task_id + if uid: + encoding = Encoding.objects.get(task_id=uid) + encoding.delete() + logger.info("deleted the Encoding object") + if encoding.temp_file: + kill_ffmpeg_process(encoding.temp_file) + + except BaseException: + pass + + return True + + +def kill_ffmpeg_process(filepath): + # this is not ideal, ffmpeg pid could be linked to the Encoding object + cmd = "ps aux|grep 'ffmpeg'|grep %s|grep -v grep |awk '{print $2}'" % filepath + result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) + pid = result.stdout.decode("utf-8").strip() + if pid: + cmd = "kill -9 %s" % pid + result = subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) + return result + + +@task(name="remove_media_file", base=Task, queue="long_tasks") +def remove_media_file(media_file=None): + rm_file(media_file) + return True + + +# TODO LIST +# 1 chunks are deleted from original server when file is fully encoded. +# however need to enter this logic in cases of fail as well +# 2 script to delete chunks in fail status +# (and check for their encdings, and delete them as well, along with +# all chunks) +# 3 beat task, remove chunks diff --git a/files/tests.py b/files/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/files/urls.py b/files/urls.py new file mode 100644 index 0000000..561242c --- /dev/null +++ b/files/urls.py @@ -0,0 +1,91 @@ +from django.conf.urls.static import static +from django.conf import settings +from django.conf.urls import url, include +from django.urls import path + +from . import views +from . import management_views +from .feeds import RssMediaFeed + +urlpatterns = [ + url(r"^$", views.index), + url(r"^about", views.about, name="about"), + url(r"^add_subtitle", views.add_subtitle, name="add_subtitle"), + url(r"^categories$", views.categories, name="categories"), + url(r"^contact$", views.contact, name="contact"), + url(r"^edit", views.edit_media, name="edit_media"), + url(r"^embed", views.embed_media, name="get_embed"), + url(r"^featured$", views.featured_media), + url(r"^fu/", include(("uploader.urls", "uploader"), namespace="uploader")), + url(r"^history$", views.history, name="history"), + url(r"^liked$", views.liked_media, name="liked_media"), + url(r"^latest$", views.latest_media), + url(r"^members", views.members, name="members"), + url( + r"^playlist/(?P[\w]*)$", + views.view_playlist, + name="get_playlist", + ), + url( + r"^playlists/(?P[\w]*)$", + views.view_playlist, + name="get_playlist", + ), + url(r"^popular$", views.recommended_media), + url(r"^recommended$", views.recommended_media), + path("rss/", RssMediaFeed()), + url(r"^search", views.search, name="search"), + url(r"^scpublisher", views.upload_media, name="upload_media"), + url(r"^tags", views.tags, name="tags"), + url(r"^tos$", views.tos, name="terms_of_service"), + url(r"^view", views.view_media, name="get_media"), + url(r"^upload", views.upload_media, name="upload_media"), + # API VIEWS + url(r"^api/v1/media$", views.MediaList.as_view()), + url(r"^api/v1/media/$", views.MediaList.as_view()), + url( + r"^api/v1/media/(?P[\w]*)$", + views.MediaDetail.as_view(), + name="api_get_media", + ), + url( + r"^api/v1/media/encoding/(?P[\w]*)$", + views.EncodingDetail.as_view(), + name="api_get_encoding", + ), + url(r"^api/v1/search$", views.MediaSearch.as_view()), + url( + r"^api/v1/media/(?P[\w]*)/actions$", + views.MediaActions.as_view(), + ), + url(r"^api/v1/categories$", views.CategoryList.as_view()), + url(r"^api/v1/tags$", views.TagList.as_view()), + url(r"^api/v1/comments$", views.CommentList.as_view()), + url( + r"^api/v1/media/(?P[\w]*)/comments$", + views.CommentDetail.as_view(), + ), + url( + r"^api/v1/media/(?P[\w]*)/comments/(?P[\w-]*)$", + views.CommentDetail.as_view(), + ), + url(r"^api/v1/playlists$", views.PlaylistList.as_view()), + url(r"^api/v1/playlists/$", views.PlaylistList.as_view()), + url( + r"^api/v1/playlists/(?P[\w]*)$", + views.PlaylistDetail.as_view(), + name="api_get_playlist", + ), + url(r"^api/v1/user/action/(?P[\w]*)$", views.UserActions.as_view()), + # ADMIN VIEWS + url(r"^api/v1/encode_profiles/$", views.EncodeProfileList.as_view()), + url(r"^api/v1/manage_media$", management_views.MediaList.as_view()), + url(r"^api/v1/manage_comments$", management_views.CommentList.as_view()), + url(r"^api/v1/manage_users$", management_views.UserList.as_view()), + url(r"^api/v1/tasks$", views.TasksList.as_view()), + url(r"^api/v1/tasks/$", views.TasksList.as_view()), + url(r"^api/v1/tasks/(?P[\w|\W]*)$", views.TaskDetail.as_view()), + url(r"^manage/comments$", views.manage_comments, name="manage_comments"), + url(r"^manage/media$", views.manage_media, name="manage_media"), + url(r"^manage/users$", views.manage_users, name="manage_users"), +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/files/views.py b/files/views.py new file mode 100644 index 0000000..3905ddf --- /dev/null +++ b/files/views.py @@ -0,0 +1,1273 @@ +from django.shortcuts import render +from django.http import HttpResponseRedirect +from django.conf import settings +from django.shortcuts import get_object_or_404 +from django.db.models import Q +from django.contrib.auth.decorators import login_required +from django.contrib import messages +from django.template.defaultfilters import slugify +from django.core.mail import EmailMessage +from django.contrib.postgres.search import SearchQuery + +from rest_framework import permissions +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.settings import api_settings +from rest_framework.exceptions import PermissionDenied +from rest_framework import status +from rest_framework.parsers import ( + JSONParser, + MultiPartParser, + FileUploadParser, + FormParser, +) + +from celery.task.control import revoke +from cms.permissions import IsAuthorizedToAdd, IsUserOrEditor +from cms.permissions import user_allowed_to_upload +from cms.custom_pagination import FastPaginationWithoutCount +from actions.models import MediaAction, USER_MEDIA_ACTIONS +from users.models import User +from .helpers import produce_ffmpeg_commands, clean_query +from .models import ( + Media, + EncodeProfile, + Encoding, + Playlist, + PlaylistMedia, + Comment, + Category, + Tag, +) +from .forms import MediaForm, ContactForm, SubtitleForm +from .tasks import save_user_action +from .methods import ( + list_tasks, + get_user_or_session, + show_recommended_media, + show_related_media, + is_mediacms_editor, + is_mediacms_manager, + update_user_ratings, + notify_user_on_comment, +) +from .serializers import ( + MediaSerializer, + CategorySerializer, + TagSerializer, + SingleMediaSerializer, + EncodeProfileSerializer, + MediaSearchSerializer, + PlaylistSerializer, + PlaylistDetailSerializer, + CommentSerializer, +) +from .stop_words import STOP_WORDS + +VALID_USER_ACTIONS = [action for action, name in USER_MEDIA_ACTIONS] + + +def about(request): + """About view""" + + context = {} + return render(request, "cms/about.html", context) + + +@login_required +def add_subtitle(request): + """Add subtitle view""" + + friendly_token = request.GET.get("m", "").strip() + if not friendly_token: + return HttpResponseRedirect("/") + media = Media.objects.filter(friendly_token=friendly_token).first() + if not media: + return HttpResponseRedirect("/") + + if not ( + request.user == media.user + or is_mediacms_editor(request.user) + or is_mediacms_manager(request.user) + ): + return HttpResponseRedirect("/") + + if request.method == "POST": + form = SubtitleForm(media, request.POST, request.FILES) + if form.is_valid(): + subtitle = form.save() + messages.add_message(request, messages.INFO, "Subtitle was added!") + return HttpResponseRedirect(subtitle.media.get_absolute_url()) + else: + form = SubtitleForm(media_item=media) + return render(request, "cms/add_subtitle.html", {"form": form}) + + +def categories(request): + """List categories view""" + + context = {} + return render(request, "cms/categories.html", context) + + +def contact(request): + """Contact view""" + + context = {} + if request.method == "GET": + form = ContactForm(request.user) + context["form"] = form + + else: + form = ContactForm(request.user, request.POST) + if form.is_valid(): + if request.user.is_authenticated: + from_email = request.user.email + name = request.user.name + else: + from_email = request.POST.get("from_email") + name = request.POST.get("name") + message = request.POST.get("message") + + title = "[{}] - Contact form message received".format(settings.PORTAL_NAME) + + msg = """ +You have received a message through the contact form\n +Sender name: %s +Sender email: %s\n +\n %s +""" % ( + name, + from_email, + message, + ) + email = EmailMessage( + title, + msg, + settings.DEFAULT_FROM_EMAIL, + settings.ADMIN_EMAIL_LIST, + reply_to=[from_email], + ) + email.send(fail_silently=True) + success_msg = "Message was sent! Thanks for contacting" + context["success_msg"] = success_msg + + return render(request, "cms/contact.html", context) + + +def history(request): + """Show personal history view""" + + context = {} + return render(request, "cms/history.html", context) + + +@login_required +def edit_media(request): + """Edit a media view""" + + friendly_token = request.GET.get("m", "").strip() + if not friendly_token: + return HttpResponseRedirect("/") + media = Media.objects.filter(friendly_token=friendly_token).first() + + if not media: + return HttpResponseRedirect("/") + + if not ( + request.user == media.user + or is_mediacms_editor(request.user) + or is_mediacms_manager(request.user) + ): + return HttpResponseRedirect("/") + if request.method == "POST": + form = MediaForm(request.user, request.POST, request.FILES, instance=media) + if form.is_valid(): + media = form.save() + for tag in media.tags.all(): + media.tags.remove(tag) + if form.cleaned_data.get("new_tags"): + for tag in form.cleaned_data.get("new_tags").split(","): + tag = slugify(tag) + if tag: + try: + tag = Tag.objects.get(title=tag) + except Tag.DoesNotExist: + tag = Tag.objects.create(title=tag, user=request.user) + if tag not in media.tags.all(): + media.tags.add(tag) + messages.add_message(request, messages.INFO, "Media was edited!") + return HttpResponseRedirect(media.get_absolute_url()) + else: + form = MediaForm(request.user, instance=media) + return render( + request, + "cms/edit_media.html", + {"form": form, "add_subtitle_url": media.add_subtitle_url}, + ) + + +def embed_media(request): + """Embed media view""" + + friendly_token = request.GET.get("m", "").strip() + if not friendly_token: + return HttpResponseRedirect("/") + + media = Media.objects.values("title").filter(friendly_token=friendly_token).first() + + if not media: + return HttpResponseRedirect("/") + + user_or_session = get_user_or_session(request) + + context = {} + context["media"] = friendly_token + return render(request, "cms/embed.html", context) + + +def featured_media(request): + """List featured media view""" + + context = {} + return render(request, "cms/featured-media.html", context) + + +def index(request): + """Index view""" + + context = {} + return render(request, "cms/index.html", context) + + +def latest_media(request): + """List latest media view""" + + context = {} + return render(request, "cms/latest-media.html", context) + + +def liked_media(request): + """List user's liked media view""" + + context = {} + return render(request, "cms/liked_media.html", context) + + +@login_required +def manage_users(request): + """List users management view""" + + context = {} + return render(request, "cms/manage_users.html", context) + + +@login_required +def manage_media(request): + """List media management view""" + + context = {} + return render(request, "cms/manage_media.html", context) + + +@login_required +def manage_comments(request): + """List comments management view""" + + context = {} + return render(request, "cms/manage_comments.html", context) + + +def members(request): + """List members view""" + + context = {} + return render(request, "cms/members.html", context) + + +def recommended_media(request): + """List recommended media view""" + + context = {} + return render(request, "cms/recommended-media.html", context) + + +def search(request): + """Search view""" + + context = {} + return render(request, "cms/search.html", context) + + +def tags(request): + """List tags view""" + + context = {} + return render(request, "cms/tags.html", context) + + +def tos(request): + """Terms of service view""" + + context = {} + return render(request, "cms/tos.html", context) + + +def upload_media(request): + """Upload media view""" + + from allauth.account.forms import LoginForm + + form = LoginForm() + context = {} + context["form"] = form + context["can_add"] = user_allowed_to_upload(request) + can_upload_exp = settings.CANNOT_ADD_MEDIA_MESSAGE + context["can_upload_exp"] = can_upload_exp + + return render(request, "cms/add-media.html", context) + + +def view_media(request): + """View media view""" + + friendly_token = request.GET.get("m", "").strip() + context = {} + media = Media.objects.filter(friendly_token=friendly_token).first() + if not media: + context["media"] = None + return render(request, "cms/media.html", context) + + user_or_session = get_user_or_session(request) + save_user_action.delay( + user_or_session, friendly_token=friendly_token, action="watch" + ) + context = {} + context["media"] = friendly_token + context["media_object"] = media + + context["CAN_DELETE_MEDIA"] = False + context["CAN_EDIT_MEDIA"] = False + context["CAN_DELETE_COMMENTS"] = False + + if request.user.is_authenticated: + if ( + (media.user.id == request.user.id) + or is_mediacms_editor(request.user) + or is_mediacms_manager(request.user) + ): + context["CAN_DELETE_MEDIA"] = True + context["CAN_EDIT_MEDIA"] = True + context["CAN_DELETE_COMMENTS"] = True + return render(request, "cms/media.html", context) + + +def view_playlist(request, friendly_token): + """View playlist view""" + + try: + playlist = Playlist.objects.get(friendly_token=friendly_token) + except BaseException: + playlist = None + + context = {} + context["playlist"] = playlist + return render(request, "cms/playlist.html", context) + + +class MediaList(APIView): + """Media listings views""" + + permission_classes = (IsAuthorizedToAdd,) + parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + + def get(self, request, format=None): + # Show media + params = self.request.query_params + show_param = params.get("show", "") + + author_param = params.get("author", "").strip() + if author_param: + user_queryset = User.objects.all() + user = get_object_or_404(user_queryset, username=author_param) + if show_param == "recommended": + pagination_class = FastPaginationWithoutCount + media = show_recommended_media(request, limit=50) + else: + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + if author_param: + # in case request.user is the user here, show + # all media independant of state + if self.request.user == user: + basic_query = Q(user=user) + else: + basic_query = Q(listable=True, user=user) + else: + # base listings should show safe content + basic_query = Q(listable=True) + + if show_param == "featured": + media = Media.objects.filter(basic_query, featured=True) + else: + media = Media.objects.filter(basic_query).order_by("-add_date") + + paginator = pagination_class() + + if show_param != "recommended": + media = media.prefetch_related("user") + page = paginator.paginate_queryset(media, request) + + serializer = MediaSerializer(page, many=True, context={"request": request}) + return paginator.get_paginated_response(serializer.data) + + def post(self, request, format=None): + # Add new media + serializer = MediaSerializer(data=request.data, context={"request": request}) + if serializer.is_valid(): + media_file = request.data["media_file"] + serializer.save(user=request.user, media_file=media_file) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +class MediaDetail(APIView): + """ + Retrieve, update or delete a media instance. + """ + + permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsUserOrEditor) + parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + + def get_object(self, friendly_token, password=None): + try: + media = ( + Media.objects.select_related("user") + .prefetch_related("encodings__profile") + .get(friendly_token=friendly_token) + ) + + # this need be explicitly called, and will call + # has_object_permission() after has_permission has succeeded + self.check_object_permissions(self.request, media) + + if media.state == "private" and not ( + self.request.user == media.user or is_mediacms_editor(self.request.user) + ): + if ( + (not password) + or (not media.password) + or (password != media.password) + ): + return Response( + {"detail": "media is private"}, + status=status.HTTP_401_UNAUTHORIZED, + ) + return media + except PermissionDenied: + return Response( + {"detail": "bad permissions"}, status=status.HTTP_401_UNAUTHORIZED + ) + except BaseException: + return Response( + {"detail": "media file does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def get(self, request, friendly_token, format=None): + # Get media details + password = request.GET.get("password") + media = self.get_object(friendly_token, password=password) + if isinstance(media, Response): + return media + + serializer = SingleMediaSerializer(media, context={"request": request}) + if media.state == "private": + related_media = [] + else: + related_media = show_related_media(media, request=request, limit=100) + related_media_serializer = MediaSerializer( + related_media, many=True, context={"request": request} + ) + related_media = related_media_serializer.data + ret = serializer.data + + # update rattings info with user specific ratings + # eg user has already rated for this media + # this only affects user rating and only if enabled + if ( + settings.ALLOW_RATINGS + and ret.get("ratings_info") + and not request.user.is_anonymous + ): + ret["ratings_info"] = update_user_ratings( + request.user, media, ret.get("ratings_info") + ) + + ret["related_media"] = related_media + return Response(ret) + + def post(self, request, friendly_token, format=None): + """superuser actions + Available only to MediaCMS editors and managers + + Action is a POST variable, review and encode are implemented + """ + + media = self.get_object(friendly_token) + if isinstance(media, Response): + return media + + if not (is_mediacms_editor(request.user) or is_mediacms_manager(request.user)): + return Response( + {"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST + ) + + action = request.data.get("type") + profiles_list = request.data.get("encoding_profiles") + result = request.data.get("result", True) + + if action == "encode": + # Create encoding tasks for specific profiles + valid_profiles = [] + if profiles_list: + if isinstance(profiles_list, list): + for p in profiles_list: + p = EncodeProfile.objects.filter(id=p).first() + if p: + valid_profiles.append(p) + elif isinstance(profiles_list, str): + try: + p = EncodeProfile.objects.filter(id=int(profiles_list)).first() + valid_profiles.append(p) + except ValueError: + return Response( + { + "detail": "encoding_profiles must be int or list of ints of valid encode profiles" + }, + status=status.HTTP_400_BAD_REQUEST, + ) + media.encode(profiles=valid_profiles) + return Response( + {"detail": "media will be encoded"}, status=status.HTTP_201_CREATED + ) + elif action == "review": + if result: + media.is_reviewed = True + elif result == False: + media.is_reviewed = False + media.save(update_fields=["is_reviewed"]) + return Response( + {"detail": "media reviewed set"}, status=status.HTTP_201_CREATED + ) + return Response( + {"detail": "not valid action or no action specified"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def put(self, request, friendly_token, format=None): + # Update a media object + media = self.get_object(friendly_token) + if isinstance(media, Response): + return media + + serializer = MediaSerializer( + media, data=request.data, context={"request": request} + ) + if serializer.is_valid(): + media_file = request.data["media_file"] + serializer.save(user=request.user, media_file=media_file) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, friendly_token, format=None): + # Delete a media object + media = self.get_object(friendly_token) + if isinstance(media, Response): + return media + media.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class MediaActions(APIView): + """ + Retrieve, update or delete a media action instance. + """ + + permission_classes = (permissions.AllowAny,) + parser_classes = (JSONParser,) + + def get_object(self, friendly_token): + try: + media = ( + Media.objects.select_related("user") + .prefetch_related("encodings__profile") + .get(friendly_token=friendly_token) + ) + if media.state == "private" and self.request.user != media.user: + return Response( + {"detail": "media is private"}, status=status.HTTP_400_BAD_REQUEST + ) + return media + except PermissionDenied: + return Response( + {"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST + ) + except BaseException: + return Response( + {"detail": "media file does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def get(self, request, friendly_token, format=None): + # show date and reason for each time media was reported + media = self.get_object(friendly_token) + if isinstance(media, Response): + return media + + ret = {} + reported = MediaAction.objects.filter(media=media, action="report") + ret["reported"] = [] + for rep in reported: + item = {"reported_date": rep.action_date, "reason": rep.extra_info} + ret["reported"].append(item) + + return Response(ret, status=status.HTTP_200_OK) + + def post(self, request, friendly_token, format=None): + # perform like/dislike/report actions + media = self.get_object(friendly_token) + if isinstance(media, Response): + return media + + action = request.data.get("type") + extra = request.data.get("extra_info") + if request.user.is_anonymous: + # there is a list of allowed actions for + # anonymous users, specified in settings + if action not in settings.ALLOW_ANONYMOUS_ACTIONS: + return Response( + {"detail": "action allowed on logged in users only"}, + status=status.HTTP_400_BAD_REQUEST, + ) + if action: + user_or_session = get_user_or_session(request) + save_user_action.delay( + user_or_session, + friendly_token=media.friendly_token, + action=action, + extra_info=extra, + ) + + return Response( + {"detail": "action received"}, status=status.HTTP_201_CREATED + ) + else: + return Response( + {"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST + ) + + def delete(self, request, friendly_token, format=None): + media = self.get_object(friendly_token) + if isinstance(media, Response): + return media + + if not request.user.is_superuser: + return Response( + {"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST + ) + + action = request.data.get("type") + if action: + if action == "report": # delete reported actions + MediaAction.objects.filter(media=media, action="report").delete() + media.reported_times = 0 + media.save(update_fields=["reported_times"]) + return Response( + {"detail": "reset reported times counter"}, + status=status.HTTP_201_CREATED, + ) + else: + return Response( + {"detail": "no action specified"}, status=status.HTTP_400_BAD_REQUEST + ) + + +class MediaSearch(APIView): + """ + Retrieve results for searc + Only GET is implemented here + """ + + parser_classes = (JSONParser,) + + def get(self, request, format=None): + params = self.request.query_params + query = params.get("q", "").strip().lower() + category = params.get("c", "").strip() + tag = params.get("t", "").strip() + + ordering = params.get("ordering", "").strip() + sort_by = params.get("sort_by", "").strip() + media_type = params.get("media_type", "").strip() + + author = params.get("author", "").strip() + + sort_by_options = ["title", "add_date", "edit_date", "views", "likes"] + if sort_by not in sort_by_options: + sort_by = "add_date" + if ordering == "asc": + ordering = "" + else: + ordering = "-" + + if media_type not in ["video", "image", "audio", "pdf"]: + media_type = None + + if not (query or category or tag): + ret = {} + return Response(ret, status=status.HTTP_200_OK) + + media = Media.objects.filter(state="public", is_reviewed=True) + + if query: + query = clean_query(query) + q_parts = [q_part for q_part in query.split() if q_part not in STOP_WORDS] + if q_parts: + query = SearchQuery(q_parts[0] + ":*", search_type="raw") + for part in q_parts[1:]: + query &= SearchQuery(part + ":*", search_type="raw") + else: + query = None + if query: + media = media.filter(search=query) + + if tag: + media = media.filter(tags__title=tag) + + if category: + media = media.filter(category__title__contains=category) + + if media_type: + media = media.filter(media_type=media_type) + + if author: + media = media.filter(user__username=author) + + media = media.order_by(f"{ordering}{sort_by}") + + if self.request.query_params.get("show", "").strip() == "titles": + media = media.values("title")[:40] + return Response(media, status=status.HTTP_200_OK) + else: + media = media.prefetch_related("user") + if category or tag: + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + else: + # pagination_class = FastPaginationWithoutCount + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + paginator = pagination_class() + page = paginator.paginate_queryset(media, request) + serializer = MediaSearchSerializer( + page, many=True, context={"request": request} + ) + return paginator.get_paginated_response(serializer.data) + + +class PlaylistList(APIView): + """Playlists listings and creation views""" + + permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAuthorizedToAdd) + parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + + def get(self, request, format=None): + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + paginator = pagination_class() + playlists = Playlist.objects.filter().prefetch_related("user") + + if "author" in self.request.query_params: + author = self.request.query_params["author"].strip() + playlists = playlists.filter(user__username=author) + + page = paginator.paginate_queryset(playlists, request) + + serializer = PlaylistSerializer(page, many=True, context={"request": request}) + return paginator.get_paginated_response(serializer.data) + + def post(self, request, format=None): + serializer = PlaylistSerializer(data=request.data, context={"request": request}) + if serializer.is_valid(): + serializer.save(user=request.user) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +class PlaylistDetail(APIView): + """Playlist related views""" + + permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsUserOrEditor) + parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + + def get_playlist(self, friendly_token): + try: + playlist = Playlist.objects.get(friendly_token=friendly_token) + self.check_object_permissions(self.request, playlist) + return playlist + except PermissionDenied: + return Response( + {"detail": "not enough permissions"}, status=status.HTTP_400_BAD_REQUEST + ) + except BaseException: + return Response( + {"detail": "Playlist does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def get(self, request, friendly_token, format=None): + playlist = self.get_playlist(friendly_token) + if isinstance(playlist, Response): + return playlist + + serializer = PlaylistDetailSerializer(playlist, context={"request": request}) + + playlist_media = PlaylistMedia.objects.filter( + playlist=playlist + ).prefetch_related("media__user") + + playlist_media = [c.media for c in playlist_media] + playlist_media_serializer = MediaSerializer( + playlist_media, many=True, context={"request": request} + ) + ret = serializer.data + ret["playlist_media"] = playlist_media_serializer.data + + return Response(ret) + + def post(self, request, friendly_token, format=None): + playlist = self.get_playlist(friendly_token) + if isinstance(playlist, Response): + return playlist + serializer = PlaylistDetailSerializer( + playlist, data=request.data, context={"request": request} + ) + if serializer.is_valid(): + serializer.save(user=request.user) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, friendly_token, format=None): + playlist = self.get_playlist(friendly_token) + if isinstance(playlist, Response): + return playlist + action = request.data.get("type") + media_friendly_token = request.data.get("media_friendly_token") + ordering = 0 + if request.data.get("ordering"): + try: + ordering = int(request.data.get("ordering")) + except ValueError: + pass + + if action in ["add", "remove", "ordering"]: + media = Media.objects.filter( + friendly_token=media_friendly_token, state="public", media_type="video" + ).first() + if media: + if action == "add": + media_in_playlist = PlaylistMedia.objects.filter( + playlist=playlist + ).count() + if media_in_playlist >= settings.MAX_MEDIA_PER_PLAYLIST: + return Response( + {"detail": "max number of media for a Playlist reached"}, + status=status.HTTP_400_BAD_REQUEST, + ) + else: + obj, created = PlaylistMedia.objects.get_or_create( + playlist=playlist, + media=media, + ordering=media_in_playlist + 1, + ) + obj.save() + return Response( + {"detail": "media added to Playlist"}, + status=status.HTTP_201_CREATED, + ) + elif action == "remove": + PlaylistMedia.objects.filter( + playlist=playlist, media=media + ).delete() + return Response( + {"detail": "media removed from Playlist"}, + status=status.HTTP_201_CREATED, + ) + elif action == "ordering": + if ordering: + playlist.set_ordering(media, ordering) + return Response( + {"detail": "new ordering set"}, + status=status.HTTP_201_CREATED, + ) + else: + return Response( + {"detail": "media is not valid"}, status=status.HTTP_400_BAD_REQUEST + ) + return Response( + {"detail": "invalid or not specified action"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def delete(self, request, friendly_token, format=None): + playlist = self.get_playlist(friendly_token) + if isinstance(playlist, Response): + return playlist + + playlist.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + + +class EncodingDetail(APIView): + """Experimental. This View is used by remote workers + Needs heavy testing and documentation. + """ + + permission_classes = (permissions.IsAdminUser,) + parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + + def post(self, request, encoding_id): + ret = {} + force = request.data.get("force", False) + task_id = request.data.get("task_id", False) + action = request.data.get("action", "") + chunk = request.data.get("chunk", False) + chunk_file_path = request.data.get("chunk_file_path", "") + + encoding_status = request.data.get("status", "") + progress = request.data.get("progress", "") + commands = request.data.get("commands", "") + logs = request.data.get("logs", "") + retries = request.data.get("retries", "") + worker = request.data.get("worker", "") + temp_file = request.data.get("temp_file", "") + total_run_time = request.data.get("total_run_time", "") + if action == "start": + try: + encoding = Encoding.objects.get(id=encoding_id) + media = encoding.media + profile = encoding.profile + except BaseException: + Encoding.objects.filter(id=encoding_id).delete() + return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST) + # TODO: break chunk True/False logic here + if ( + Encoding.objects.filter( + media=media, + profile=profile, + chunk=chunk, + chunk_file_path=chunk_file_path, + ).count() + > 1 + and force == False + ): + Encoding.objects.filter(id=encoding_id).delete() + return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST) + else: + Encoding.objects.filter( + media=media, + profile=profile, + chunk=chunk, + chunk_file_path=chunk_file_path, + ).exclude(id=encoding.id).delete() + + encoding.status = "running" + if task_id: + encoding.task_id = task_id + + encoding.save() + if chunk: + original_media_path = chunk_file_path + original_media_md5sum = encoding.md5sum + original_media_url = ( + settings.SSL_FRONTEND_HOST + encoding.media_chunk_url + ) + else: + original_media_path = media.media_file.path + original_media_md5sum = media.md5sum + original_media_url = ( + settings.SSL_FRONTEND_HOST + media.original_media_url + ) + + ret["original_media_url"] = original_media_url + ret["original_media_path"] = original_media_path + ret["original_media_md5sum"] = original_media_md5sum + + # generating the commands here, and will replace these with temporary + # files created on the remote server + tf = "TEMP_FILE_REPLACE" + tfpass = "TEMP_FPASS_FILE_REPLACE" + ffmpeg_commands = produce_ffmpeg_commands( + original_media_path, + media.media_info, + resolution=profile.resolution, + codec=profile.codec, + output_filename=tf, + pass_file=tfpass, + chunk=chunk, + ) + if not ffmpeg_commands: + encoding.delete() + return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST) + + ret["duration"] = media.duration + ret["ffmpeg_commands"] = ffmpeg_commands + ret["profile_extension"] = profile.extension + return Response(ret, status=status.HTTP_201_CREATED) + elif action == "update_fields": + try: + encoding = Encoding.objects.get(id=encoding_id) + except BaseException: + return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST) + to_update = ["size", "update_date"] + if encoding_status: + encoding.status = encoding_status + to_update.append("status") + if progress: + encoding.progress = progress + to_update.append("progress") + if logs: + encoding.logs = logs + to_update.append("logs") + if commands: + encoding.commands = commands + to_update.append("commands") + if task_id: + encoding.task_id = task_id + to_update.append("task_id") + if total_run_time: + encoding.total_run_time = total_run_time + to_update.append("total_run_time") + if worker: + encoding.worker = worker + to_update.append("worker") + if temp_file: + encoding.temp_file = temp_file + to_update.append("temp_file") + + if retries: + encoding.retries = retries + to_update.append("retries") + + try: + encoding.save(update_fields=to_update) + except BaseException: + return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST) + return Response({"status": "success"}, status=status.HTTP_201_CREATED) + + def put(self, request, encoding_id, format=None): + encoding_file = request.data["file"] + encoding = Encoding.objects.filter(id=encoding_id).first() + if not encoding: + return Response( + {"detail": "encoding does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + encoding.media_file = encoding_file + encoding.save() + return Response({"detail": "ok"}, status=status.HTTP_201_CREATED) + + +class CommentList(APIView): + permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsAuthorizedToAdd) + parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + + def get(self, request, format=None): + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + paginator = pagination_class() + comments = Comment.objects.filter() + comments = comments.prefetch_related("user") + comments = comments.prefetch_related("media") + params = self.request.query_params + if "author" in params: + author_param = params["author"].strip() + user_queryset = User.objects.all() + user = get_object_or_404(user_queryset, username=author_param) + comments = comments.filter(user=user) + + page = paginator.paginate_queryset(comments, request) + + serializer = CommentSerializer(page, many=True, context={"request": request}) + return paginator.get_paginated_response(serializer.data) + + +class CommentDetail(APIView): + """Comments related views + Listings of comments for a media (GET) + Create comment (POST) + Delete comment (DELETE) + """ + + permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsUserOrEditor) + parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + + def get_object(self, friendly_token): + try: + media = Media.objects.select_related("user").get( + friendly_token=friendly_token + ) + self.check_object_permissions(self.request, media) + if media.state == "private" and self.request.user != media.user: + return Response( + {"detail": "media is private"}, status=status.HTTP_400_BAD_REQUEST + ) + return media + except PermissionDenied: + return Response( + {"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST + ) + except BaseException: + return Response( + {"detail": "media file does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def get(self, request, friendly_token): + # list comments for a media + media = self.get_object(friendly_token) + if isinstance(media, Response): + return media + comments = media.comments.filter().prefetch_related("user") + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + paginator = pagination_class() + page = paginator.paginate_queryset(comments, request) + serializer = CommentSerializer(page, many=True, context={"request": request}) + return paginator.get_paginated_response(serializer.data) + + def delete(self, request, friendly_token, uid=None): + """Delete a comment + Administrators, MediaCMS editors and managers, + media owner, and comment owners, can delete a comment + """ + if uid: + try: + comment = Comment.objects.get(uid=uid) + except BaseException: + return Response( + {"detail": "comment does not exist"}, + status=status.HTTP_400_BAD_REQUEST, + ) + if ( + (comment.user == self.request.user) + or comment.media.user == self.request.user + or is_mediacms_editor(self.request.user) + ): + comment.delete() + else: + return Response( + {"detail": "bad permissions"}, status=status.HTTP_400_BAD_REQUEST + ) + return Response(status=status.HTTP_204_NO_CONTENT) + + def post(self, request, friendly_token): + """Create a comment""" + media = self.get_object(friendly_token) + if isinstance(media, Response): + return media + + if not media.enable_comments: + return Response( + {"detail": "comments not allowed here"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + serializer = CommentSerializer(data=request.data, context={"request": request}) + if serializer.is_valid(): + serializer.save(user=request.user, media=media) + if request.user != media.user: + notify_user_on_comment(friendly_token=media.friendly_token) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + +class UserActions(APIView): + parser_classes = (JSONParser,) + + def get(self, request, action): + media = [] + if action in VALID_USER_ACTIONS: + if request.user.is_authenticated: + media = ( + Media.objects.select_related("user") + .filter( + mediaactions__user=request.user, mediaactions__action=action + ) + .order_by("-mediaactions__action_date") + ) + elif request.session.session_key: + media = ( + Media.objects.select_related("user") + .filter( + mediaactions__session_key=request.session.session_key, + mediaactions__action=action, + ) + .order_by("-mediaactions__action_date") + ) + + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + paginator = pagination_class() + page = paginator.paginate_queryset(media, request) + serializer = MediaSerializer(page, many=True, context={"request": request}) + return paginator.get_paginated_response(serializer.data) + + +class CategoryList(APIView): + """List categories""" + + def get(self, request, format=None): + categories = Category.objects.filter().order_by("title") + serializer = CategorySerializer( + categories, many=True, context={"request": request} + ) + ret = serializer.data + return Response(ret) + + +class TagList(APIView): + """List tags""" + + def get(self, request, format=None): + tags = Tag.objects.filter().order_by("-media_count") + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + paginator = pagination_class() + page = paginator.paginate_queryset(tags, request) + serializer = TagSerializer(page, many=True, context={"request": request}) + return paginator.get_paginated_response(serializer.data) + + +class EncodeProfileList(APIView): + """List encode profiles""" + + def get(self, request, format=None): + profiles = EncodeProfile.objects.all() + serializer = EncodeProfileSerializer( + profiles, many=True, context={"request": request} + ) + return Response(serializer.data) + + +class TasksList(APIView): + """List tasks""" + + permission_classes = (permissions.IsAdminUser,) + + def get(self, request, format=None): + ret = list_tasks() + return Response(ret) + + +class TaskDetail(APIView): + """Cancel a task""" + + permission_classes = (permissions.IsAdminUser,) + + def delete(self, request, uid, format=None): + revoke(uid, terminate=True) + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/fixtures/categories.json b/fixtures/categories.json new file mode 100644 index 0000000..23dbac5 --- /dev/null +++ b/fixtures/categories.json @@ -0,0 +1 @@ +[{"model": "files.category", "pk": 1, "fields": {"uid": "6a376886-4fdb-4d68-a4f3-b2c978fa6b08", "add_date": "2020-04-11T18:06:32.397Z", "title": "Art", "description": "", "user": null, "is_global": false, "media_count": 0, "thumbnail": ""}}, {"model": "files.category", "pk": 2, "fields": {"uid": "3067680e-b3d9-4e8e-8d55-a868e9f2b8a5", "add_date": "2020-04-11T18:06:36.768Z", "title": "Documentary", "description": "", "user": null, "is_global": false, "media_count": 0, "thumbnail": ""}}, {"model": "files.category", "pk": 3, "fields": {"uid": "3fb841f8-2baa-4b92-890a-8ca7bcd3fa40", "add_date": "2020-04-11T18:06:42.009Z", "title": "Experimental", "description": "", "user": null, "is_global": false, "media_count": 0, "thumbnail": ""}}, {"model": "files.category", "pk": 4, "fields": {"uid": "b7a1a749-a13e-489a-adf8-ee1c514b1677", "add_date": "2020-04-11T18:06:52.826Z", "title": "Film", "description": "", "user": null, "is_global": false, "media_count": 0, "thumbnail": ""}}, {"model": "files.category", "pk": 5, "fields": {"uid": "0073814e-a4dd-42a6-a5a8-9d219606be6b", "add_date": "2020-04-11T18:06:57.486Z", "title": "Music", "description": "", "user": null, "is_global": false, "media_count": 0, "thumbnail": ""}}, {"model": "files.category", "pk": 6, "fields": {"uid": "38534d33-7116-4ce9-9d96-9cde60744b9a", "add_date": "2020-04-11T18:07:05.455Z", "title": "TV", "description": "", "user": null, "is_global": false, "media_count": 0, "thumbnail": ""}}] diff --git a/fixtures/encoding_profiles.json b/fixtures/encoding_profiles.json new file mode 100644 index 0000000..c983103 --- /dev/null +++ b/fixtures/encoding_profiles.json @@ -0,0 +1 @@ +[{"model": "files.encodeprofile", "pk": 19, "fields": {"name": "h264-2160", "extension": "mp4", "resolution": 2160, "codec": "h264", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 2, "fields": {"name": "vp9-2160", "extension": "webm", "resolution": 2160, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 16, "fields": {"name": "h265-2160", "extension": "mp4", "resolution": 2160, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 4, "fields": {"name": "h264-1440", "extension": "mp4", "resolution": 1440, "codec": "h264", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 5, "fields": {"name": "vp9-1440", "extension": "webm", "resolution": 1440, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 6, "fields": {"name": "h265-1440", "extension": "mp4", "resolution": 1440, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 7, "fields": {"name": "h264-1080", "extension": "mp4", "resolution": 1080, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 8, "fields": {"name": "vp9-1080", "extension": "webm", "resolution": 1080, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 9, "fields": {"name": "h265-1080", "extension": "mp4", "resolution": 1080, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 10, "fields": {"name": "h264-720", "extension": "mp4", "resolution": 720, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 11, "fields": {"name": "vp9-720", "extension": "webm", "resolution": 720, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 12, "fields": {"name": "h265-720", "extension": "mp4", "resolution": 720, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 13, "fields": {"name": "h264-480", "extension": "mp4", "resolution": 480, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 14, "fields": {"name": "vp9-480", "extension": "webm", "resolution": 480, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 15, "fields": {"name": "h265-480", "extension": "mp4", "resolution": 480, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 3, "fields": {"name": "h264-360", "extension": "mp4", "resolution": 360, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 17, "fields": {"name": "vp9-360", "extension": "webm", "resolution": 360, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 18, "fields": {"name": "h265-360", "extension": "mp4", "resolution": 360, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 2, "fields": {"name": "h264-240", "extension": "mp4", "resolution": 240, "codec": "h264", "description": "", "active": true}}, {"model": "files.encodeprofile", "pk": 20, "fields": {"name": "vp9-240", "extension": "webm", "resolution": 240, "codec": "vp9", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 21, "fields": {"name": "h265-240", "extension": "mp4", "resolution": 240, "codec": "h265", "description": "", "active": false}}, {"model": "files.encodeprofile", "pk": 1, "fields": {"name": "preview", "extension": "gif", "resolution": null, "codec": null, "description": "", "active": true}}] diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..38caa4b --- /dev/null +++ b/install.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# should be run as root and only on Ubuntu 18/20 versions! +echo "Welcome to the MediacMS installation!"; + +if [ `id -u` -ne 0 ] + then echo "Please run as root" + exit +fi + + +while true; do + read -p " +This script will attempt to perform a system update, install required dependencies, install and configure PostgreSQL, NGINX, Redis and a few other utilities. +It is expected to run on a new system **with no running instances of any these services**. Make sure you check the script before you continue. Then enter yes or no +" yn + case $yn in + [Yy]* ) echo "OK!"; break;; + [Nn]* ) echo "Have a great day"; exit;; + * ) echo "Please answer yes or no.";; + esac +done + + +if [[ `lsb_release -d` == *"Ubuntu 20"* ]]; then + echo 'Performing system update and dependency installation, this will take a few minutes' + apt-get update && apt-get -y upgrade && apt install python3-venv python3-dev virtualenv redis-server postgresql nginx git gcc vim unzip ffmpeg imagemagick python3-certbot-nginx certbot -y +elif [[ `lsb_release -d` = *"Ubuntu 18"* ]]; then + echo 'Performing system update and dependency installation, this will take a few minutes' + apt-get update && apt-get -y upgrade && apt install python3-venv python3-dev virtualenv redis-server postgresql nginx git gcc vim unzip ffmpeg imagemagick python3-certbot-nginx certbot -y +else + echo "This script is tested for Ubuntu 18 and 20 versions only, if you want to try MediaCMS on another system you have to perform the manual installation" +exit +fi + +read -p "Enter portal URL, or press enter for localhost : " FRONTEND_HOST +read -p "Enter portal name, or press enter for 'MediaCMS : " PORTAL_NAME + +[ -z "$PORTAL_NAME" ] && PORTAL_NAME='MediaCMS' +[ -z "$FRONTEND_HOST" ] && FRONTEND_HOST='localhost' + +echo 'Creating database to be used in MediaCMS' + +su -c "psql -c \"CREATE DATABASE mediacms\"" postgres +su -c "psql -c \"CREATE USER mediacms WITH ENCRYPTED PASSWORD 'mediacms'\"" postgres +su -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE mediacms TO mediacms\"" postgres + +echo 'Creating python virtualenv on /home/mediacms.io' + +cd /home/mediacms.io +virtualenv . --python=python3 +source /home/mediacms.io/bin/activate +cd mediacms +pip install -r requirements.txt + +SECRET_KEY=`python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'` + +# remove http or https prefix +FRONTEND_HOST=`echo "$FRONTEND_HOST" | sed -r 's/http:\/\///g'` +FRONTEND_HOST=`echo "$FRONTEND_HOST" | sed -r 's/https:\/\///g'` + +sed -i s/localhost/$FRONTEND_HOST/g deploy/mediacms.io + + +echo 'FRONTEND_HOST='\'"$FRONTEND_HOST"\' >> cms/local_settings.py +echo 'PORTAL_NAME='\'"$PORTAL_NAME"\' >> cms/local_settings.py +echo "SSL_FRONTEND_HOST = FRONTEND_HOST.replace('http', 'https')" >> cms/local_settings.py + +echo 'SECRET_KEY='\'"$SECRET_KEY"\' >> cms/local_settings.py + +mkdir logs +mkdir pids +python manage.py migrate +python manage.py loaddata fixtures/encoding_profiles.json +python manage.py loaddata fixtures/categories.json +python manage.py collectstatic --noinput + +ADMIN_PASS=`python -c "import secrets;chars = 'abcdefghijklmnopqrstuvwxyz0123456789';print(''.join(secrets.choice(chars) for i in range(10)))"` +echo "from users.models import User; User.objects.create_superuser('admin', 'admin@example.com', '$ADMIN_PASS')" | python manage.py shell + +echo "from django.contrib.sites.models import Site; Site.objects.update(name='$FRONTEND_HOST', domain='$FRONTEND_HOST')" | python manage.py shell + +chown -R www-data. /home/mediacms.io/ +cp deploy/celery_long.service /etc/systemd/system/celery_long.service && systemctl enable celery_long && systemctl start celery_long +cp deploy/celery_short.service /etc/systemd/system/celery_short.service && systemctl enable celery_short && systemctl start celery_short +cp deploy/celery_beat.service /etc/systemd/system/celery_beat.service && systemctl enable celery_beat &&systemctl start celery_beat +cp deploy/mediacms.service /etc/systemd/system/mediacms.service && systemctl enable mediacms.service && systemctl start mediacms.service + +mkdir -p /etc/letsencrypt/live/mediacms.io/ +mkdir -p /etc/letsencrypt/live/$FRONTEND_HOST +cp deploy/mediacms.io_fullchain.pem /etc/letsencrypt/live/$FRONTEND_HOST/fullchain.pem +cp deploy/mediacms.io_privkey.pem /etc/letsencrypt/live/$FRONTEND_HOST/privkey.pem +cp deploy/mediacms.io /etc/nginx/sites-available/default +cp deploy/mediacms.io /etc/nginx/sites-enabled/default +cp deploy/uwsgi_params /etc/nginx/sites-enabled/uwsgi_params +cp deploy/nginx.conf /etc/nginx/ +systemctl stop nginx +systemctl start nginx + +# attempt to get a valid certificate for specified domain + +if [ "$FRONTEND_HOST" != "localhost" ]; then + echo 'attempt to get a valid certificate for specified url $FRONTEND_HOST' + certbot --nginx -n --agree-tos --register-unsafely-without-email -d $FRONTEND_HOST + certbot --nginx -n --agree-tos --register-unsafely-without-email -d $FRONTEND_HOST + # unfortunately for some reason it needs to be run two times in order to create the entries + # and directory structure!!! + systemctl restart nginx +else + echo "will not call certbot utility to update ssl certificate for url 'localhost', using default ssl certificate" +fi + + +# Bento4 utility installation, for HLS + +cd /home/mediacms.io/mediacms +wget http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-632.x86_64-unknown-linux.zip +unzip Bento4-SDK-1-6-0-632.x86_64-unknown-linux.zip +mkdir /home/mediacms.io/mediacms/media_files/hls + +# last, set default owner +chown -R www-data. /home/mediacms.io/ + +echo 'MediaCMS installation completed, open browser on http://'"$FRONTEND_HOST"' and login with user admin and password '"$ADMIN_PASS"'' diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..cf1a63a --- /dev/null +++ b/manage.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cms.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1f2523b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,32 @@ +Django==3.1.4 +djangorestframework==3.12.2 +django-allauth==0.44.0 + +psycopg2-binary==2.8.6 + +uwsgi==2.0.19.1 + +django-redis==4.12.1 +celery==4.4.7 + +Pillow==8.0.1 +django-imagekit +markdown +django-filter +filetype +django-mptt +django-crispy-forms +requests==2.25.0 +django-celery-email +m3u8 + +django-ckeditor + +# extra nice utilities! +rpdb +tqdm +ipython +flake8 +pep8 +django-silk +django-debug-toolbar diff --git a/uploader/__init__.py b/uploader/__init__.py new file mode 100644 index 0000000..3e2f46a --- /dev/null +++ b/uploader/__init__.py @@ -0,0 +1 @@ +__version__ = "0.9.0" diff --git a/uploader/apps.py b/uploader/apps.py new file mode 100644 index 0000000..72a7575 --- /dev/null +++ b/uploader/apps.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 +from django.apps import AppConfig + + +class UploaderConfig(AppConfig): + name = "uploader" diff --git a/uploader/fineuploader.py b/uploader/fineuploader.py new file mode 100644 index 0000000..fd1bfae --- /dev/null +++ b/uploader/fineuploader.py @@ -0,0 +1,96 @@ +from os.path import join +from io import StringIO +import shutil +from django.conf import settings + +from . import utils + + +class BaseFineUploader(object): + def __init__(self, data, *args, **kwargs): + self.data = data + self.total_filesize = data.get("qqtotalfilesize") + self.filename = data.get("qqfilename") + self.uuid = data.get("qquuid") + self.file = data.get("qqfile") + self.storage_class = settings.FILE_STORAGE + self.real_path = None + + @property + def finished(self): + return self.real_path is not None + + @property + def file_path(self): + return join(settings.UPLOAD_DIR, self.uuid) + + @property + def _full_file_path(self): + return join(self.file_path, self.filename) + + @property + def storage(self): + file_storage = utils.import_class(self.storage_class) + return file_storage() + + @property + def url(self): + if not self.finished: + return None + return self.storage.url(self.real_path) + + +class ChunkedFineUploader(BaseFineUploader): + concurrent = True + + def __init__(self, data, concurrent=True, *args, **kwargs): + super(ChunkedFineUploader, self).__init__(data, *args, **kwargs) + self.concurrent = concurrent + self.total_parts = data.get("qqtotalparts") + if not isinstance(self.total_parts, int): + self.total_parts = 1 + self.part_index = data.get("qqpartindex") + + @property + def chunks_path(self): + return join(settings.CHUNKS_DIR, self.uuid) + + @property + def _abs_chunks_path(self): + return join(settings.MEDIA_ROOT, self.chunks_path) + + @property + def chunk_file(self): + return join(self.chunks_path, str(self.part_index)) + + @property + def chunked(self): + return self.total_parts > 1 + + @property + def is_time_to_combine_chunks(self): + return self.total_parts - 1 == self.part_index + + def combine_chunks(self): + # implement the same behaviour. + self.real_path = self.storage.save(self._full_file_path, StringIO()) + with self.storage.open(self.real_path, "wb") as final_file: + for i in range(self.total_parts): + part = join(self.chunks_path, str(i)) + with self.storage.open(part, "rb") as source: + final_file.write(source.read()) + shutil.rmtree(self._abs_chunks_path) + + def _save_chunk(self): + return self.storage.save(self.chunk_file, self.file) + + def save(self): + if self.chunked: + chunk = self._save_chunk() + if not self.concurrent and self.is_time_to_combine_chunks: + self.combine_chunks() + return self.real_path + return chunk + else: + self.real_path = self.storage.save(self._full_file_path, self.file) + return self.real_path diff --git a/uploader/forms.py b/uploader/forms.py new file mode 100644 index 0000000..e89175d --- /dev/null +++ b/uploader/forms.py @@ -0,0 +1,19 @@ +from django import forms + + +class FineUploaderUploadForm(forms.Form): + qqfile = forms.FileField() + qquuid = forms.CharField() + qqfilename = forms.CharField() + qqpartindex = forms.IntegerField(required=False) + qqchunksize = forms.IntegerField(required=False) + qqtotalparts = forms.IntegerField(required=False) + qqtotalfilesize = forms.IntegerField(required=False) + qqpartbyteoffset = forms.IntegerField(required=False) + + +class FineUploaderUploadSuccessForm(forms.Form): + qquuid = forms.CharField() + qqfilename = forms.CharField() + qqtotalparts = forms.IntegerField() + qqtotalfilesize = forms.IntegerField(required=False) diff --git a/uploader/models.py b/uploader/models.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/uploader/models.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/uploader/urls.py b/uploader/urls.py new file mode 100644 index 0000000..2e0d06a --- /dev/null +++ b/uploader/urls.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +from django.conf.urls import url + +from . import views + +app_name = "uploader" + +urlpatterns = [ + url(r"^upload/$", views.FineUploaderView.as_view(), name="upload"), +] diff --git a/uploader/utils.py b/uploader/utils.py new file mode 100644 index 0000000..f752c96 --- /dev/null +++ b/uploader/utils.py @@ -0,0 +1,22 @@ +from django.core.exceptions import ImproperlyConfigured +from importlib import import_module + + +def import_class(path): + path_bits = path.split(".") + + if len(path_bits) < 2: + message = "'{0}' is not a complete Python path.".format(path) + raise ImproperlyConfigured(message) + + class_name = path_bits.pop() + module_path = ".".join(path_bits) + module_itself = import_module(module_path) + + if not hasattr(module_itself, class_name): + message = "The Python module '{}' has no '{}' class.".format( + module_path, class_name + ) + raise ImportError(message) + + return getattr(module_itself, class_name) diff --git a/uploader/views.py b/uploader/views.py new file mode 100644 index 0000000..93b2da1 --- /dev/null +++ b/uploader/views.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +import os +import shutil + +from django.http import JsonResponse +from django.views import generic +from django.conf import settings +from django.core.files import File +from django.core.exceptions import PermissionDenied + +from cms.permissions import user_allowed_to_upload +from files.models import Media +from files.helpers import rm_file +from .forms import FineUploaderUploadForm, FineUploaderUploadSuccessForm +from .fineuploader import ChunkedFineUploader + + +class FineUploaderView(generic.FormView): + http_method_names = ("post",) + form_class_upload = FineUploaderUploadForm + form_class_upload_success = FineUploaderUploadSuccessForm + + @property + def concurrent(self): + return settings.CONCURRENT_UPLOADS + + @property + def chunks_done(self): + return self.chunks_done_param_name in self.request.GET + + @property + def chunks_done_param_name(self): + return settings.CHUNKS_DONE_PARAM_NAME + + def make_response(self, data, **kwargs): + return JsonResponse(data, **kwargs) + + def get_form(self, form_class=None): + if self.chunks_done: + form_class = self.form_class_upload_success + else: + form_class = self.form_class_upload + return form_class(**self.get_form_kwargs()) + + def dispatch(self, request, *args, **kwargs): + if not user_allowed_to_upload(request): + raise PermissionDenied # HTTP 403 + return super(FineUploaderView, self).dispatch(request, *args, **kwargs) + + def form_valid(self, form): + self.upload = ChunkedFineUploader(form.cleaned_data, self.concurrent) + if self.upload.concurrent and self.chunks_done: + try: + self.upload.combine_chunks() + except FileNotFoundError: + data = {"success": False, "error": "Error with File Uploading"} + return self.make_response(data, status=400) + elif self.upload.total_parts == 1: + self.upload.save() + else: + self.upload.save() + return self.make_response({"success": True}) + # create media! + media_file = os.path.join(settings.MEDIA_ROOT, self.upload.real_path) + with open(media_file, "rb") as f: + myfile = File(f) + new = Media.objects.create(media_file=myfile, user=self.request.user) + rm_file(media_file) + shutil.rmtree(os.path.join(settings.MEDIA_ROOT, self.upload.file_path)) + return self.make_response( + {"success": True, "media_url": new.get_absolute_url()} + ) + + def form_invalid(self, form): + data = {"success": False, "error": "%s" % repr(form.errors)} + return self.make_response(data, status=400) diff --git a/users/__init__.py b/users/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/users/adapter.py b/users/adapter.py new file mode 100644 index 0000000..0e35a99 --- /dev/null +++ b/users/adapter.py @@ -0,0 +1,22 @@ +from django.urls import reverse +from django.conf import settings +from allauth.account.adapter import DefaultAccountAdapter +from django.core.exceptions import ValidationError + + +class MyAccountAdapter(DefaultAccountAdapter): + def get_email_confirmation_url_stub(self, request, emailconfirmation): + url = reverse("account_confirm_email", args=[emailconfirmation.key]) + return settings.SSL_FRONTEND_HOST + url + + def clean_email(self, email): + if email.split("@")[1] in settings.RESTRICTED_DOMAINS_FOR_USER_REGISTRATION: + raise ValidationError("Domain is restricted from registering") + return email + + def is_open_for_signup(self, request): + return settings.USERS_CAN_SELF_REGISTER + + def send_mail(self, template_prefix, email, context): + msg = self.render_mail(template_prefix, email, context) + msg.send(fail_silently=True) diff --git a/users/admin.py b/users/admin.py new file mode 100644 index 0000000..04c453b --- /dev/null +++ b/users/admin.py @@ -0,0 +1,38 @@ +from django.contrib import admin + +from .models import User + + +class UserAdmin(admin.ModelAdmin): + search_fields = ["email", "username", "name"] + exclude = ( + "user_permissions", + "title", + "password", + "groups", + "last_login", + "is_featured", + "location", + "first_name", + "last_name", + "media_count", + "date_joined", + "is_staff", + "is_active", + ) + list_display = [ + "username", + "name", + "email", + "logo", + "date_added", + "is_superuser", + "is_editor", + "is_manager", + "media_count", + ] + list_filter = ["is_superuser", "is_editor", "is_manager"] + ordering = ("-date_added",) + + +admin.site.register(User, UserAdmin) diff --git a/users/apps.py b/users/apps.py new file mode 100644 index 0000000..3ef1284 --- /dev/null +++ b/users/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + name = "users" diff --git a/users/forms.py b/users/forms.py new file mode 100644 index 0000000..1113ba4 --- /dev/null +++ b/users/forms.py @@ -0,0 +1,59 @@ +from django import forms +from .models import User, Channel + + +class SignupForm(forms.Form): + name = forms.CharField(max_length=100, label="Name") + + def signup(self, request, user): + user.name = self.cleaned_data["name"] + user.save() + + +class UserForm(forms.ModelForm): + class Meta: + model = User + fields = ( + "name", + "description", + "email", + "logo", + "notification_on_comments", + "is_featured", + "advancedUser", + "is_manager", + "is_editor", + #"allow_contact", + ) + + def clean_logo(self): + image = self.cleaned_data.get("logo", False) + if image: + if image.size > 2 * 1024 * 1024: + raise forms.ValidationError("Image file too large ( > 2mb )") + return image + else: + raise forms.ValidationError("Please provide a logo") + + def __init__(self, user, *args, **kwargs): + super(UserForm, self).__init__(*args, **kwargs) + self.fields.pop("is_featured") + if not user.is_superuser: + self.fields.pop("advancedUser") + self.fields.pop("is_manager") + self.fields.pop("is_editor") + + +class ChannelForm(forms.ModelForm): + class Meta: + model = Channel + fields = ("banner_logo",) + + def clean_banner_logo(self): + image = self.cleaned_data.get("banner_logo", False) + if image: + if image.size > 2 * 1024 * 1024: + raise forms.ValidationError("Image file too large ( > 2mb )") + return image + else: + raise forms.ValidationError("Please provide a banner") diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py new file mode 100644 index 0000000..e577d9f --- /dev/null +++ b/users/migrations/0001_initial.py @@ -0,0 +1,283 @@ +# Generated by Django 3.1.4 on 2020-12-01 07:12 + +from django.conf import settings +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import imagekit.models.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] + + operations = [ + migrations.CreateModel( + name="User", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=150, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ( + "logo", + imagekit.models.fields.ProcessedImageField( + blank=True, + default="userlogos/user.jpg", + upload_to="userlogos/%Y/%m/%d", + ), + ), + ("description", models.TextField(blank=True, verbose_name="About me")), + ( + "name", + models.CharField( + db_index=True, max_length=250, verbose_name="full name" + ), + ), + ( + "date_added", + models.DateTimeField( + db_index=True, + default=django.utils.timezone.now, + verbose_name="date added", + ), + ), + ( + "is_featured", + models.BooleanField( + db_index=True, default=False, verbose_name="Is featured" + ), + ), + ( + "title", + models.CharField(blank=True, max_length=250, verbose_name="Title"), + ), + ( + "advancedUser", + models.BooleanField( + db_index=True, default=False, verbose_name="advanced user" + ), + ), + ("media_count", models.IntegerField(default=0)), + ( + "notification_on_comments", + models.BooleanField( + default=True, + verbose_name="Whether you will receive email notifications for comments added to your content", + ), + ), + ( + "allow_contact", + models.BooleanField( + default=False, + verbose_name="Whether allow contact will be shown on profile page", + ), + ), + ( + "location", + models.CharField( + blank=True, max_length=250, verbose_name="Location" + ), + ), + ( + "is_editor", + models.BooleanField( + db_index=True, default=False, verbose_name="MediaCMS Editor" + ), + ), + ( + "is_manager", + models.BooleanField( + db_index=True, default=False, verbose_name="MediaCMS Manager" + ), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.Group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.Permission", + verbose_name="user permissions", + ), + ), + ], + options={ + "ordering": ["-date_added", "name"], + }, + managers=[ + ("objects", django.contrib.auth.models.UserManager()), + ], + ), + migrations.CreateModel( + name="Notification", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("action", models.CharField(blank=True, max_length=30)), + ("notify", models.BooleanField(default=False)), + ( + "method", + models.CharField( + choices=[("email", "Email")], default="email", max_length=20 + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.CreateModel( + name="Channel", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(db_index=True, max_length=90)), + ("description", models.TextField(blank=True, help_text="description")), + ("add_date", models.DateTimeField(auto_now_add=True, db_index=True)), + ("friendly_token", models.CharField(blank=True, max_length=12)), + ( + "banner_logo", + imagekit.models.fields.ProcessedImageField( + blank=True, + default="userlogos/banner.jpg", + upload_to="userlogos/%Y/%m/%d", + ), + ), + ( + "subscribers", + models.ManyToManyField( + blank=True, + related_name="subscriptions", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="channels", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.AddIndex( + model_name="user", + index=models.Index( + fields=["-date_added", "name"], name="users_user_date_ad_4eb0b8_idx" + ), + ), + ] diff --git a/users/migrations/__init__.py b/users/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/users/models.py b/users/models.py new file mode 100644 index 0000000..dd087ad --- /dev/null +++ b/users/models.py @@ -0,0 +1,220 @@ +from django.db import models +from django.conf import settings +from django.contrib.auth.models import AbstractUser +from django.utils import timezone +from django.urls import reverse +from django.dispatch import receiver +from django.db.models.signals import post_save, post_delete +from django.utils.html import strip_tags +from django.core.mail import EmailMessage + +from imagekit.processors import ResizeToFill +from imagekit.models import ProcessedImageField + +import files.helpers as helpers +from files.models import Media, Tag, Category + + +class User(AbstractUser): + logo = ProcessedImageField( + upload_to="userlogos/%Y/%m/%d", + processors=[ResizeToFill(200, 200)], + default="userlogos/user.jpg", + format="JPEG", + options={"quality": 75}, + blank=True, + ) + description = models.TextField("About me", blank=True) + + name = models.CharField("full name", max_length=250, db_index=True) + date_added = models.DateTimeField("date added", default=timezone.now, db_index=True) + is_featured = models.BooleanField("Is featured", default=False, db_index=True) + + title = models.CharField("Title", max_length=250, blank=True) + advancedUser = models.BooleanField("advanced user", default=False, db_index=True) + media_count = models.IntegerField(default=0) # save number of videos + notification_on_comments = models.BooleanField( + "Whether you will receive email notifications for comments added to your content", + default=True, + ) + location = models.CharField("Location", max_length=250, blank=True) + is_editor = models.BooleanField("MediaCMS Editor", default=False, db_index=True) + is_manager = models.BooleanField("MediaCMS Manager", default=False, db_index=True) + allow_contact = models.BooleanField( + "Whether allow contact will be shown on profile page", default=False + ) + + class Meta: + ordering = ["-date_added", "name"] + indexes = [models.Index(fields=["-date_added", "name"])] + + def update_user_media(self): + self.media_count = Media.objects.filter(listable=True, user=self).count() + self.save(update_fields=["media_count"]) + return True + + def thumbnail_url(self): + if self.logo: + return helpers.url_from_path(self.logo.path) + return None + + def banner_thumbnail_url(self): + c = self.channels.filter().order_by("add_date").first() + if c: + return helpers.url_from_path(c.banner_logo.path) + return None + + @property + def email_is_verified(self): + if self.emailaddress_set.first(): + if self.emailaddress_set.first().verified: + return True + return False + + def get_absolute_url(self, api=False): + if api: + return reverse("api_get_user", kwargs={"username": self.username}) + else: + return reverse("get_user", kwargs={"username": self.username}) + + def edit_url(self): + return reverse("edit_user", kwargs={"username": self.username}) + + def default_channel_edit_url(self): + c = self.channels.filter().order_by("add_date").first() + if c: + return reverse("edit_channel", kwargs={"friendly_token": c.friendly_token}) + return None + + @property + def playlists_info(self): + ret = [] + for playlist in self.playlists.all(): + c = {} + c["title"] = playlist.title + c["description"] = playlist.description + c["media_count"] = playlist.media_count + c["add_date"] = playlist.add_date + c["url"] = playlist.get_absolute_url() + ret.append(c) + return ret + + @property + def media_info(self): + ret = {} + results = [] + ret["results"] = results + ret["user_media"] = "/api/v1/media?author={0}".format(self.username) + return ret + + def save(self, *args, **kwargs): + strip_text_items = ["name", "description", "title"] + for item in strip_text_items: + setattr(self, item, strip_tags(getattr(self, item, None))) + super(User, self).save(*args, **kwargs) + + +class Channel(models.Model): + title = models.CharField(max_length=90, db_index=True) + description = models.TextField(blank=True, help_text="description") + user = models.ForeignKey( + User, on_delete=models.CASCADE, db_index=True, related_name="channels" + ) + add_date = models.DateTimeField(auto_now_add=True, db_index=True) + subscribers = models.ManyToManyField(User, related_name="subscriptions", blank=True) + friendly_token = models.CharField(blank=True, max_length=12) + banner_logo = ProcessedImageField( + upload_to="userlogos/%Y/%m/%d", + processors=[ResizeToFill(900, 200)], + default="userlogos/banner.jpg", + format="JPEG", + options={"quality": 85}, + blank=True, + ) + + def save(self, *args, **kwargs): + strip_text_items = ["description", "title"] + for item in strip_text_items: + setattr(self, item, strip_tags(getattr(self, item, None))) + + if not self.friendly_token: + while True: + friendly_token = helpers.produce_friendly_token() + if not Channel.objects.filter(friendly_token=friendly_token): + self.friendly_token = friendly_token + break + super(Channel, self).save(*args, **kwargs) + + def __str__(self): + return "{0} -{1}".format(self.user.username, self.title) + + def get_absolute_url(self, edit=False): + if edit: + return reverse( + "edit_channel", kwargs={"friendly_token": self.friendly_token} + ) + else: + return reverse( + "view_channel", kwargs={"friendly_token": self.friendly_token} + ) + + @property + def edit_url(self): + return self.get_absolute_url(edit=True) + + +@receiver(post_save, sender=User) +def post_user_create(sender, instance, created, **kwargs): + # create a Channel object upon user creation, name it default + if created: + new = Channel.objects.create(title="default", user=instance) + new.save() + if settings.ADMINS_NOTIFICATIONS.get("NEW_USER", False): + title = "[{}] - New user just registered".format(settings.PORTAL_NAME) + msg = """ +User has just registered with email %s\n +Visit user profile page at %s + """ % ( + instance.email, + settings.SSL_FRONTEND_HOST + instance.get_absolute_url(), + ) + email = EmailMessage( + title, msg, settings.DEFAULT_FROM_EMAIL, settings.ADMIN_EMAIL_LIST + ) + email.send(fail_silently=True) + + +NOTIFICATION_METHODS = (("email", "Email"),) + + +class Notification(models.Model): + """User specific notifications + To be exposed on user profile + Needs work + """ + + user = models.ForeignKey( + User, on_delete=models.CASCADE, db_index=True, related_name="notifications" + ) + action = models.CharField(max_length=30, blank=True) + notify = models.BooleanField(default=False) + method = models.CharField( + max_length=20, choices=NOTIFICATION_METHODS, default="email" + ) + + def save(self, *args, **kwargs): + super(Notification, self).save(*args, **kwargs) + + def __str__(self): + return self.user.username + + +@receiver(post_delete, sender=User) +def delete_content(sender, instance, **kwargs): + """Delete user related content + Upon user deletion + """ + + Media.objects.filter(user=instance).delete() + Tag.objects.filter(user=instance).delete() + Category.objects.filter(user=instance).delete() diff --git a/users/serializers.py b/users/serializers.py new file mode 100644 index 0000000..a26d77b --- /dev/null +++ b/users/serializers.py @@ -0,0 +1,82 @@ +from rest_framework import serializers +from .models import User + + +class UserSerializer(serializers.ModelSerializer): + url = serializers.SerializerMethodField() + api_url = serializers.SerializerMethodField() + thumbnail_url = serializers.SerializerMethodField() + + def get_url(self, obj): + return self.context["request"].build_absolute_uri(obj.get_absolute_url()) + + def get_api_url(self, obj): + return self.context["request"].build_absolute_uri( + obj.get_absolute_url(api=True) + ) + + def get_thumbnail_url(self, obj): + return self.context["request"].build_absolute_uri(obj.thumbnail_url()) + + class Meta: + model = User + read_only_fields = ( + "date_added", + "is_featured", + "uid", + "username", + "advancedUser", + "is_editor", + "is_manager", + "email_is_verified", + ) + fields = ( + "description", + "date_added", + "name", + "is_featured", + "thumbnail_url", + "url", + "api_url", + "username", + "advancedUser", + "is_editor", + "is_manager", + "email_is_verified", + ) + + +class UserDetailSerializer(serializers.ModelSerializer): + url = serializers.SerializerMethodField() + api_url = serializers.SerializerMethodField() + thumbnail_url = serializers.SerializerMethodField() + + def get_url(self, obj): + return self.context["request"].build_absolute_uri(obj.get_absolute_url()) + + def get_api_url(self, obj): + return self.context["request"].build_absolute_uri( + obj.get_absolute_url(api=True) + ) + + def get_thumbnail_url(self, obj): + return self.context["request"].build_absolute_uri(obj.thumbnail_url()) + + class Meta: + model = User + read_only_fields = ("date_added", "is_featured", "uid", "username") + fields = ( + "description", + "date_added", + "name", + "is_featured", + "thumbnail_url", + "banner_thumbnail_url", + "url", + "username", + "media_info", + "api_url", + "edit_url", + "default_channel_edit_url", + ) + extra_kwargs = {"name": {"required": False}} diff --git a/users/tests.py b/users/tests.py new file mode 100644 index 0000000..e69de29 diff --git a/users/urls.py b/users/urls.py new file mode 100644 index 0000000..f1a8322 --- /dev/null +++ b/users/urls.py @@ -0,0 +1,43 @@ +from django.conf.urls import url +from . import views + +urlpatterns = [ + url(r"^user/(?P[\w@._-]*)$", views.view_user, name="get_user"), + url( + r"^user/(?P[\w@.]*)/media$", + views.view_user_media, + name="get_user_media", + ), + url( + r"^user/(?P[\w@.]*)/playlists$", + views.view_user_playlists, + name="get_user_playlists", + ), + url( + r"^user/(?P[\w@.]*)/about$", + views.view_user_about, + name="get_user_about", + ), + url(r"^user/(?P[\w@.]*)/edit$", views.edit_user, name="edit_user"), + url( + r"^channel/(?P[\w]*)$", views.view_channel, name="view_channel" + ), + url( + r"^channel/(?P[\w]*)/edit$", + views.edit_channel, + name="edit_channel", + ), + # API VIEWS + url(r"^api/v1/users$", views.UserList.as_view(), name="api_users"), + url(r"^api/v1/users/$", views.UserList.as_view()), + url( + r"^api/v1/users/(?P[\w@._-]*)$", + views.UserDetail.as_view(), + name="api_get_user", + ), + url( + r"^api/v1/users/(?P[\w@._-]*)/contact", + views.contact_user, + name="api_contact_user", + ), +] diff --git a/users/validators.py b/users/validators.py new file mode 100644 index 0000000..734e8f2 --- /dev/null +++ b/users/validators.py @@ -0,0 +1,18 @@ +import re + +from django.core import validators +from django.utils.deconstruct import deconstructible +from django.utils.translation import gettext_lazy as _ + + +@deconstructible +class ASCIIUsernameValidator(validators.RegexValidator): + regex = r"^[\w]+$" + message = _( + "Enter a valid username. This value may contain only " + "English letters and numbers" + ) + flags = re.ASCII + + +custom_username_validators = [ASCIIUsernameValidator()] diff --git a/users/views.py b/users/views.py new file mode 100644 index 0000000..50f2da8 --- /dev/null +++ b/users/views.py @@ -0,0 +1,296 @@ +from django.shortcuts import render +from django.http import HttpResponseRedirect +from django.contrib.auth.decorators import login_required +from django.core.mail import EmailMessage +from django.conf import settings + +from rest_framework import permissions +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.settings import api_settings +from rest_framework.exceptions import PermissionDenied +from rest_framework import status +from rest_framework.parsers import ( + JSONParser, + MultiPartParser, + FileUploadParser, + FormParser, +) +from rest_framework.decorators import api_view +from cms.permissions import IsUserOrManager +from files.methods import is_mediacms_manager, is_mediacms_editor +from .models import User, Channel +from .forms import UserForm, ChannelForm +from .serializers import UserSerializer, UserDetailSerializer + + +def get_user(username): + try: + user = User.objects.get(username=username) + return user + except User.DoesNotExist: + return None + + +def view_user(request, username): + context = {} + user = get_user(username=username) + if not user: + return HttpResponseRedirect("/members") + context["user"] = user + context["CAN_EDIT"] = ( + True + if ((user and user == request.user) or is_mediacms_manager(request.user)) + else False + ) + context["CAN_DELETE"] = True if is_mediacms_manager(request.user) else False + context["SHOW_CONTACT_FORM"] = ( + True if (user.allow_contact or is_mediacms_editor(request.user)) else False + ) + return render(request, "cms/user.html", context) + + +def view_user_media(request, username): + context = {} + user = get_user(username=username) + if not user: + return HttpResponseRedirect("/members") + + context["user"] = user + context["CAN_EDIT"] = ( + True + if ((user and user == request.user) or request.user.is_superuser) + else False + ) + context["CAN_DELETE"] = True if request.user.is_superuser else False + context["SHOW_CONTACT_FORM"] = ( + True if (user.allow_contact or is_mediacms_editor(request.user)) else False + ) + return render(request, "cms/user_media.html", context) + + +def view_user_playlists(request, username): + context = {} + user = get_user(username=username) + if not user: + return HttpResponseRedirect("/members") + + context["user"] = user + context["CAN_EDIT"] = ( + True + if ((user and user == request.user) or request.user.is_superuser) + else False + ) + context["CAN_DELETE"] = True if request.user.is_superuser else False + context["SHOW_CONTACT_FORM"] = ( + True if (user.allow_contact or is_mediacms_editor(request.user)) else False + ) + + return render(request, "cms/user_playlists.html", context) + + +def view_user_about(request, username): + context = {} + user = get_user(username=username) + if not user: + return HttpResponseRedirect("/members") + + context["user"] = user + context["CAN_EDIT"] = ( + True + if ((user and user == request.user) or request.user.is_superuser) + else False + ) + context["CAN_DELETE"] = True if request.user.is_superuser else False + context["SHOW_CONTACT_FORM"] = ( + True if (user.allow_contact or is_mediacms_editor(request.user)) else False + ) + + return render(request, "cms/user_about.html", context) + + +@login_required +def edit_user(request, username): + user = get_user(username=username) + if not user or (user != request.user and not is_mediacms_manager(request.user)): + return HttpResponseRedirect("/") + + if request.method == "POST": + form = UserForm(request.user, request.POST, request.FILES, instance=user) + if form.is_valid(): + user = form.save(commit=False) + user.save() + return HttpResponseRedirect(user.get_absolute_url()) + else: + form = UserForm(request.user, instance=user) + return render(request, "cms/user_edit.html", {"form": form}) + + +def view_channel(request, friendly_token): + context = {} + channel = Channel.objects.filter(friendly_token=friendly_token).first() + if not channel: + user = None + else: + user = channel.user + context["user"] = user + context["CAN_EDIT"] = ( + True + if ((user and user == request.user) or request.user.is_superuser) + else False + ) + return render(request, "cms/channel.html", context) + + +@login_required +def edit_channel(request, friendly_token): + channel = Channel.objects.filter(friendly_token=friendly_token).first() + if not ( + channel and request.user.is_authenticated and (request.user == channel.user) + ): + return HttpResponseRedirect("/") + + if request.method == "POST": + form = ChannelForm(request.POST, request.FILES, instance=channel) + if form.is_valid(): + channel = form.save(commit=False) + channel.save() + return HttpResponseRedirect(request.user.get_absolute_url()) + else: + form = ChannelForm(instance=channel) + return render(request, "cms/channel_edit.html", {"form": form}) + + +@api_view(["POST"]) +def contact_user(request, username): + if not request.user.is_authenticated: + return Response( + {"detail": "request need be authenticated"}, + status=status.HTTP_401_UNAUTHORIZED, + ) + user = User.objects.filter(username=username).first() + if user and (user.allow_contact or is_mediacms_editor(request.user)): + subject = request.data.get("subject") + from_email = request.user.email + subject = f"[{settings.PORTAL_NAME}] - Message from {from_email}" + body = request.data.get("body") + body = """ +You have received a message through the contact form\n +Sender name: %s +Sender email: %s\n +\n %s +""" % ( + request.user.name, + from_email, + body, + ) + email = EmailMessage( + subject, + body, + settings.DEFAULT_FROM_EMAIL, + [user.email], + reply_to=[from_email], + ) + email.send(fail_silently=True) + + return Response(status=status.HTTP_204_NO_CONTENT) + + +class UserList(APIView): + permission_classes = (permissions.IsAuthenticatedOrReadOnly,) + parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + + def get(self, request, format=None): + pagination_class = api_settings.DEFAULT_PAGINATION_CLASS + paginator = pagination_class() + users = User.objects.filter() + location = request.GET.get("location", "").strip() + if location: + users = users.filter(location=location) + + page = paginator.paginate_queryset(users, request) + + serializer = UserSerializer(page, many=True, context={"request": request}) + return paginator.get_paginated_response(serializer.data) + + +class UserDetail(APIView): + """""" + + permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsUserOrManager) + parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser) + + def get_user(self, username): + try: + user = User.objects.get(username=username) + # this need be explicitly called, and will call + # has_object_permission() after has_permission has succeeded + self.check_object_permissions(self.request, user) + return user + except PermissionDenied: + return Response( + {"detail": "not enough permissions"}, status=status.HTTP_400_BAD_REQUEST + ) + except User.DoesNotExist: + return Response( + {"detail": "user does not exist"}, status=status.HTTP_400_BAD_REQUEST + ) + + def get(self, request, username, format=None): + # Get user details + user = self.get_user(username) + if isinstance(user, Response): + return user + + serializer = UserDetailSerializer(user, context={"request": request}) + return Response(serializer.data) + + def post(self, request, uid, format=None): + # USER + user = self.get_user(uid) + if isinstance(user, Response): + return user + + serializer = UserDetailSerializer( + user, data=request.data, context={"request": request} + ) + if serializer.is_valid(): + logo = request.data.get("logo") + if logo: + serializer.save(logo=logo) + else: + serializer.save() + + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, uid, format=None): + # ADMIN + user = self.get_user(uid) + if isinstance(user, Response): + return user + + if not request.user.is_superuser: + return Response( + {"detail": "not allowed"}, status=status.HTTP_400_BAD_REQUEST + ) + + action = request.data.get("action") + if action == "feature": + user.is_featured = True + user.save() + elif action == "unfeature": + user.is_featured = False + user.save() + + serializer = UserDetailSerializer(user, context={"request": request}) + return Response(serializer.data) + + def delete(self, request, username, format=None): + # Delete a user + user = self.get_user(username) + if isinstance(user, Response): + return user + + user.delete() + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/uwsgi.ini b/uwsgi.ini new file mode 100644 index 0000000..63f692e --- /dev/null +++ b/uwsgi.ini @@ -0,0 +1,27 @@ +[uwsgi] + +chdir = /home/mediacms.io/mediacms/ +virtualenv = /home/mediacms.io +module = cms.wsgi + +uid=www-data +gid=www-data + +processes = 2 +threads = 2 + +master = true + +socket = 127.0.0.1:9000 +#socket = /home/mediacms.io/mediacms/deploy/uwsgi.sock + + +workers = 2 + + +vacuum = true + +logto = /home/mediacms.io/mediacms/logs/errorlog.txt + +disable-logging = true +