How to use platform_name method in molecule

Best Python code snippet using molecule_python

webview.py

Source:webview.py Github

copy

Full Screen

1# pylint: disable=bad-continuation2"""3Certificate HTML webview.4"""5from datetime import datetime6from uuid import uuid47import logging8import urllib9from django.conf import settings10from django.contrib.auth.models import User11from django.http import HttpResponse, Http40412from django.template import RequestContext13from django.utils.translation import ugettext as _14from django.utils.encoding import smart_str15from badges.events.course_complete import get_completion_badge16from badges.utils import badges_enabled17from courseware.access import has_access18from edxmako.shortcuts import render_to_response19from edxmako.template import Template20from eventtracking import tracker21from opaque_keys import InvalidKeyError22from opaque_keys.edx.keys import CourseKey23from openedx.core.lib.courses import course_image_url24from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers25from student.models import LinkedInAddToProfileConfiguration26from util import organizations_helpers as organization_api27from util.views import handle_50028from xmodule.modulestore.django import modulestore29from xmodule.modulestore.exceptions import ItemNotFoundError30from certificates.api import (31 get_active_web_certificate,32 get_certificate_url,33 emit_certificate_event,34 has_html_certificates_enabled,35 get_certificate_template,36 get_certificate_header_context,37 get_certificate_footer_context,38)39from certificates.models import (40 GeneratedCertificate,41 CertificateStatuses,42 CertificateHtmlViewConfiguration,43 CertificateSocialNetworks)44log = logging.getLogger(__name__)45def get_certificate_description(mode, certificate_type, platform_name):46 """47 :return certificate_type_description on the basis of current mode48 """49 certificate_type_description = None50 if mode == 'honor':51 # Translators: This text describes the 'Honor' course certificate type.52 certificate_type_description = _("An {cert_type} certificate signifies that a "53 "learner has agreed to abide by the honor code established by {platform_name} "54 "and has completed all of the required tasks for this course under its "55 "guidelines.").format(cert_type=certificate_type,56 platform_name=platform_name)57 elif mode == 'verified':58 # Translators: This text describes the 'ID Verified' course certificate type, which is a higher level of59 # verification offered by edX. This type of verification is useful for professional education/certifications60 certificate_type_description = _("A {cert_type} certificate signifies that a "61 "learner has agreed to abide by the honor code established by {platform_name} "62 "and has completed all of the required tasks for this course under its "63 "guidelines. A {cert_type} certificate also indicates that the "64 "identity of the learner has been checked and "65 "is valid.").format(cert_type=certificate_type,66 platform_name=platform_name)67 elif mode == 'xseries':68 # Translators: This text describes the 'XSeries' course certificate type. An XSeries is a collection of69 # courses related to each other in a meaningful way, such as a specific topic or theme, or even an organization70 certificate_type_description = _("An {cert_type} certificate demonstrates a high level of "71 "achievement in a program of study, and includes verification of "72 "the student's identity.").format(cert_type=certificate_type)73 return certificate_type_description74def _update_certificate_context(context, user_certificate, platform_name):75 """76 Build up the certificate web view context using the provided values77 (Helper method to keep the view clean)78 """79 # Populate dynamic output values using the course/certificate data loaded above80 certificate_type = context.get('certificate_type')81 # Override the defaults with any mode-specific static values82 context['certificate_id_number'] = user_certificate.verify_uuid83 context['certificate_verify_url'] = "{prefix}{uuid}{suffix}".format(84 prefix=context.get('certificate_verify_url_prefix'),85 uuid=user_certificate.verify_uuid,86 suffix=context.get('certificate_verify_url_suffix')87 )88 # Translators: The format of the date includes the full name of the month89 context['certificate_date_issued'] = _('{month} {day}, {year}').format(90 month=user_certificate.modified_date.strftime("%B"),91 day=user_certificate.modified_date.day,92 year=user_certificate.modified_date.year93 )94 # Translators: This text represents the verification of the certificate95 context['document_meta_description'] = _('This is a valid {platform_name} certificate for {user_name}, '96 'who participated in {partner_short_name} {course_number}').format(97 platform_name=platform_name,98 user_name=context['accomplishment_copy_name'],99 partner_short_name=context['organization_short_name'],100 course_number=context['course_number']101 )102 # Translators: This text is bound to the HTML 'title' element of the page and appears in the browser title bar103 context['document_title'] = _("{partner_short_name} {course_number} Certificate | {platform_name}").format(104 partner_short_name=context['organization_short_name'],105 course_number=context['course_number'],106 platform_name=platform_name107 )108 # Translators: This text fragment appears after the student's name (displayed in a large font) on the certificate109 # screen. The text describes the accomplishment represented by the certificate information displayed to the user110 context['accomplishment_copy_description_full'] = _("successfully completed, received a passing grade, and was "111 "awarded this {platform_name} {certificate_type} "112 "Certificate of Completion in ").format(113 platform_name=platform_name,114 certificate_type=context.get("certificate_type"))115 certificate_type_description = get_certificate_description(user_certificate.mode, certificate_type, platform_name)116 if certificate_type_description:117 context['certificate_type_description'] = certificate_type_description118 # Translators: This text describes the purpose (and therefore, value) of a course certificate119 context['certificate_info_description'] = _("{platform_name} acknowledges achievements through "120 "certificates, which are awarded for course activities "121 "that {platform_name} students complete.").format(122 platform_name=platform_name,123 tos_url=context.get('company_tos_url'),124 verified_cert_url=context.get('company_verified_certificate_url'))125def _update_context_with_basic_info(context, course_id, platform_name, configuration):126 """127 Updates context dictionary with basic info required before rendering simplest128 certificate templates.129 """130 context['platform_name'] = platform_name131 context['course_id'] = course_id132 # Update the view context with the default ConfigurationModel settings133 context.update(configuration.get('default', {}))134 # Translators: 'All rights reserved' is a legal term used in copyrighting to protect published content135 reserved = _("All rights reserved")136 context['copyright_text'] = u'© {year} {platform_name}. {reserved}.'.format(137 year=settings.COPYRIGHT_YEAR,138 platform_name=platform_name,139 reserved=reserved140 )141 # Translators: This text is bound to the HTML 'title' element of the page and appears142 # in the browser title bar when a requested certificate is not found or recognized143 context['document_title'] = _("Invalid Certificate")144 # Translators: The & characters represent an ampersand character and can be ignored145 context['company_tos_urltext'] = _("Terms of Service & Honor Code")146 # Translators: A 'Privacy Policy' is a legal document/statement describing a website's use of personal information147 context['company_privacy_urltext'] = _("Privacy Policy")148 # Translators: This line appears as a byline to a header image and describes the purpose of the page149 context['logo_subtitle'] = _("Certificate Validation")150 # Translators: Accomplishments describe the awards/certifications obtained by students on this platform151 context['accomplishment_copy_about'] = _('About {platform_name} Accomplishments').format(152 platform_name=platform_name153 )154 # Translators: This line appears on the page just before the generation date for the certificate155 context['certificate_date_issued_title'] = _("Issued On:")156 # Translators: The Certificate ID Number is an alphanumeric value unique to each individual certificate157 context['certificate_id_number_title'] = _('Certificate ID Number')158 context['certificate_info_title'] = _('About {platform_name} Certificates').format(159 platform_name=platform_name160 )161 context['certificate_verify_title'] = _("How {platform_name} Validates Student Certificates").format(162 platform_name=platform_name163 )164 # Translators: This text describes the validation mechanism for a certificate file (known as GPG security)165 context['certificate_verify_description'] = _('Certificates issued by {platform_name} are signed by a gpg key so '166 'that they can be validated independently by anyone with the '167 '{platform_name} public key. For independent verification, '168 '{platform_name} uses what is called a '169 '"detached signature""".').format(platform_name=platform_name)170 context['certificate_verify_urltext'] = _("Validate this certificate for yourself")171 # Translators: This text describes (at a high level) the mission and charter the edX platform and organization172 context['company_about_description'] = _("{platform_name} offers interactive online classes and MOOCs.").format(173 platform_name=platform_name)174 context['company_about_title'] = _("About {platform_name}").format(platform_name=platform_name)175 context['company_about_urltext'] = _("Learn more about {platform_name}").format(platform_name=platform_name)176 context['company_courselist_urltext'] = _("Learn with {platform_name}").format(platform_name=platform_name)177 context['company_careers_urltext'] = _("Work at {platform_name}").format(platform_name=platform_name)178 context['company_contact_urltext'] = _("Contact {platform_name}").format(platform_name=platform_name)179 # Translators: This text appears near the top of the certficate and describes the guarantee provided by edX180 context['document_banner'] = _("{platform_name} acknowledges the following student accomplishment").format(181 platform_name=platform_name182 )183def _update_course_context(request, context, course, platform_name):184 """185 Updates context dictionary with course info.186 """187 context['full_course_image_url'] = request.build_absolute_uri(course_image_url(course))188 course_title_from_cert = context['certificate_data'].get('course_title', '')189 accomplishment_copy_course_name = course_title_from_cert if course_title_from_cert else course.display_name190 context['accomplishment_copy_course_name'] = accomplishment_copy_course_name191 course_number = course.display_coursenumber if course.display_coursenumber else course.number192 context['course_number'] = course_number193 if context['organization_long_name']:194 # Translators: This text represents the description of course195 context['accomplishment_copy_course_description'] = _('a course of study offered by {partner_short_name}, '196 'an online learning initiative of '197 '{partner_long_name}.').format(198 partner_short_name=context['organization_short_name'],199 partner_long_name=context['organization_long_name'],200 platform_name=platform_name)201 else:202 # Translators: This text represents the description of course203 context['accomplishment_copy_course_description'] = _('a course of study offered by '204 '{partner_short_name}.').format(205 partner_short_name=context['organization_short_name'],206 platform_name=platform_name)207def _update_social_context(request, context, course, user, user_certificate, platform_name):208 """209 Updates context dictionary with info required for social sharing.210 """211 share_settings = configuration_helpers.get_value("SOCIAL_SHARING_SETTINGS", settings.SOCIAL_SHARING_SETTINGS)212 context['facebook_share_enabled'] = share_settings.get('CERTIFICATE_FACEBOOK', False)213 context['facebook_app_id'] = configuration_helpers.get_value("FACEBOOK_APP_ID", settings.FACEBOOK_APP_ID)214 context['facebook_share_text'] = share_settings.get(215 'CERTIFICATE_FACEBOOK_TEXT',216 _("I completed the {course_title} course on {platform_name}.").format(217 course_title=context['accomplishment_copy_course_name'],218 platform_name=platform_name219 )220 )221 context['twitter_share_enabled'] = share_settings.get('CERTIFICATE_TWITTER', False)222 context['twitter_share_text'] = share_settings.get(223 'CERTIFICATE_TWITTER_TEXT',224 _("I completed a course at {platform_name}. Take a look at my certificate.").format(225 platform_name=platform_name226 )227 )228 share_url = request.build_absolute_uri(get_certificate_url(course_id=course.id, uuid=user_certificate.verify_uuid))229 context['share_url'] = share_url230 twitter_url = ''231 if context.get('twitter_share_enabled', False):232 twitter_url = 'https://twitter.com/intent/tweet?text={twitter_share_text}&url={share_url}'.format(233 twitter_share_text=smart_str(context['twitter_share_text']),234 share_url=urllib.quote_plus(smart_str(share_url))235 )236 context['twitter_url'] = twitter_url237 context['linked_in_url'] = None238 # If enabled, show the LinkedIn "add to profile" button239 # Clicking this button sends the user to LinkedIn where they240 # can add the certificate information to their profile.241 linkedin_config = LinkedInAddToProfileConfiguration.current()242 linkedin_share_enabled = share_settings.get('CERTIFICATE_LINKEDIN', linkedin_config.enabled)243 if linkedin_share_enabled:244 context['linked_in_url'] = linkedin_config.add_to_profile_url(245 course.id,246 course.display_name,247 user_certificate.mode,248 smart_str(share_url)249 )250def _update_context_with_user_info(context, user, user_certificate):251 """252 Updates context dictionary with user related info.253 """254 user_fullname = user.profile.name255 context['username'] = user.username256 context['course_mode'] = user_certificate.mode257 context['accomplishment_user_id'] = user.id258 context['accomplishment_copy_name'] = user_fullname259 context['accomplishment_copy_username'] = user.username260 context['accomplishment_more_title'] = _("More Information About {user_name}'s Certificate:").format(261 user_name=user_fullname262 )263 # Translators: This line is displayed to a user who has completed a course and achieved a certification264 context['accomplishment_banner_opening'] = _("{fullname}, you earned a certificate!").format(265 fullname=user_fullname266 )267 # Translators: This line congratulates the user and instructs them to share their accomplishment on social networks268 context['accomplishment_banner_congrats'] = _("Congratulations! This page summarizes what "269 "you accomplished. Show it off to family, friends, and colleagues "270 "in your social and professional networks.")271 # Translators: This line leads the reader to understand more about the certificate that a student has been awarded272 context['accomplishment_copy_more_about'] = _("More about {fullname}'s accomplishment").format(273 fullname=user_fullname274 )275def _get_user_certificate(request, user, course_key, course, preview_mode=None):276 """277 Retrieves user's certificate from db. Creates one in case of preview mode.278 Returns None if there is no certificate generated for given user279 otherwise returns `GeneratedCertificate` instance.280 """281 user_certificate = None282 if preview_mode:283 # certificate is being previewed from studio284 if has_access(request.user, 'instructor', course) or has_access(request.user, 'staff', course):285 user_certificate = GeneratedCertificate(286 mode=preview_mode,287 verify_uuid=unicode(uuid4().hex),288 modified_date=datetime.now().date()289 )290 else:291 # certificate is being viewed by learner or public292 try:293 user_certificate = GeneratedCertificate.eligible_certificates.get(294 user=user,295 course_id=course_key,296 status=CertificateStatuses.downloadable297 )298 except GeneratedCertificate.DoesNotExist:299 pass300 return user_certificate301def _track_certificate_events(request, context, course, user, user_certificate):302 """303 Tracks web certificate view related events.304 """305 # Badge Request Event Tracking Logic306 course_key = course.location.course_key307 if 'evidence_visit' in request.GET:308 badge_class = get_completion_badge(course_key, user)309 if not badge_class:310 log.warning('Visit to evidence URL for badge, but badges not configured for course "%s"', course_key)311 badges = []312 else:313 badges = badge_class.get_for_user(user)314 if badges:315 # There should only ever be one of these.316 badge = badges[0]317 tracker.emit(318 'edx.badge.assertion.evidence_visited',319 {320 'badge_name': badge.badge_class.display_name,321 'badge_slug': badge.badge_class.slug,322 'badge_generator': badge.backend,323 'issuing_component': badge.badge_class.issuing_component,324 'user_id': user.id,325 'course_id': unicode(course_key),326 'enrollment_mode': badge.badge_class.mode,327 'assertion_id': badge.id,328 'assertion_image_url': badge.image_url,329 'assertion_json_url': badge.assertion_url,330 'issuer': badge.data.get('issuer'),331 }332 )333 else:334 log.warn(335 "Could not find badge for %s on course %s.",336 user.id,337 course_key,338 )339 # track certificate evidence_visited event for analytics when certificate_user and accessing_user are different340 if request.user and request.user.id != user.id:341 emit_certificate_event('evidence_visited', user, unicode(course.id), course, {342 'certificate_id': user_certificate.verify_uuid,343 'enrollment_mode': user_certificate.mode,344 'social_network': CertificateSocialNetworks.linkedin345 })346def _render_certificate_template(request, context, course, user_certificate):347 """348 Picks appropriate certificate templates and renders it.349 """350 if settings.FEATURES.get('CUSTOM_CERTIFICATE_TEMPLATES_ENABLED', False):351 custom_template = get_certificate_template(course.id, user_certificate.mode)352 if custom_template:353 template = Template(354 custom_template,355 output_encoding='utf-8',356 input_encoding='utf-8',357 default_filters=['decode.utf8'],358 encoding_errors='replace',359 )360 context = RequestContext(request, context)361 return HttpResponse(template.render(context))362 return render_to_response("certificates/valid.html", context)363def _update_configuration_context(context, configuration):364 """365 Site Configuration will need to be able to override any hard coded366 content that was put into the context in the367 _update_certificate_context() call above. For example the368 'company_about_description' talks about edX, which we most likely369 do not want to keep in configurations.370 So we need to re-apply any configuration/content that371 we are sourcing from the database. This is somewhat duplicative of372 the code at the beginning of this method, but we373 need the configuration at the top as some error code paths374 require that to be set up early on in the pipeline375 """376 config_key = configuration_helpers.get_value('domain_prefix')377 config = configuration.get("microsites", {})378 if config_key and config:379 context.update(config.get(config_key, {}))380def _update_badge_context(context, course, user):381 """382 Updates context with badge info.383 """384 badge = None385 if badges_enabled() and course.issue_badges:386 badges = get_completion_badge(course.location.course_key, user).get_for_user(user)387 if badges:388 badge = badges[0]389 context['badge'] = badge390def _update_organization_context(context, course):391 """392 Updates context with organization related info.393 """394 partner_long_name, organization_logo = None, None395 partner_short_name = course.display_organization if course.display_organization else course.org396 organizations = organization_api.get_course_organizations(course_id=course.id)397 if organizations:398 #TODO Need to add support for multiple organizations, Currently we are interested in the first one.399 organization = organizations[0]400 partner_long_name = organization.get('name', partner_long_name)401 partner_short_name = organization.get('short_name', partner_short_name)402 organization_logo = organization.get('logo', None)403 context['organization_long_name'] = partner_long_name404 context['organization_short_name'] = partner_short_name405 context['accomplishment_copy_course_org'] = partner_short_name406 context['organization_logo'] = organization_logo407def render_cert_by_uuid(request, certificate_uuid):408 """409 This public view generates an HTML representation of the specified certificate410 """411 try:412 certificate = GeneratedCertificate.eligible_certificates.get(413 verify_uuid=certificate_uuid,414 status=CertificateStatuses.downloadable415 )416 return render_html_view(request, certificate.user.id, unicode(certificate.course_id))417 except GeneratedCertificate.DoesNotExist:418 raise Http404419@handle_500(420 template_path="certificates/server-error.html",421 test_func=lambda request: request.GET.get('preview', None)422)423def render_html_view(request, user_id, course_id):424 """425 This public view generates an HTML representation of the specified user and course426 If a certificate is not available, we display a "Sorry!" screen instead427 """428 try:429 user_id = int(user_id)430 except ValueError:431 raise Http404432 preview_mode = request.GET.get('preview', None)433 platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME)434 configuration = CertificateHtmlViewConfiguration.get_config()435 # Create the initial view context, bootstrapping with Django settings and passed-in values436 context = {}437 _update_context_with_basic_info(context, course_id, platform_name, configuration)438 invalid_template_path = 'certificates/invalid.html'439 # Kick the user back to the "Invalid" screen if the feature is disabled440 if not has_html_certificates_enabled(course_id):441 log.info(442 "Invalid cert: HTML certificates disabled for %s. User id: %d",443 course_id,444 user_id,445 )446 return render_to_response(invalid_template_path, context)447 # Load the course and user objects448 try:449 course_key = CourseKey.from_string(course_id)450 user = User.objects.get(id=user_id)451 course = modulestore().get_course(course_key)452 # For any other expected exceptions, kick the user back to the "Invalid" screen453 except (InvalidKeyError, ItemNotFoundError, User.DoesNotExist) as exception:454 error_str = (455 "Invalid cert: error finding course %s or user with id "456 "%d. Specific error: %s"457 )458 log.info(error_str, course_id, user_id, str(exception))459 return render_to_response(invalid_template_path, context)460 # Load user's certificate461 user_certificate = _get_user_certificate(request, user, course_key, course, preview_mode)462 if not user_certificate:463 log.info(464 "Invalid cert: User %d does not have eligible cert for %s.",465 user_id,466 course_id,467 )468 return render_to_response(invalid_template_path, context)469 # Get the active certificate configuration for this course470 # If we do not have an active certificate, we'll need to send the user to the "Invalid" screen471 # Passing in the 'preview' parameter, if specified, will return a configuration, if defined472 active_configuration = get_active_web_certificate(course, preview_mode)473 if active_configuration is None:474 log.info(475 "Invalid cert: course %s does not have an active configuration. User id: %d",476 course_id,477 user_id,478 )479 return render_to_response(invalid_template_path, context)480 context['certificate_data'] = active_configuration481 # Append/Override the existing view context values with any mode-specific ConfigurationModel values482 context.update(configuration.get(user_certificate.mode, {}))483 # Append organization info484 _update_organization_context(context, course)485 # Append course info486 _update_course_context(request, context, course, platform_name)487 # Append user info488 _update_context_with_user_info(context, user, user_certificate)489 # Append social sharing info490 _update_social_context(request, context, course, user, user_certificate, platform_name)491 # Append/Override the existing view context values with certificate specific values492 _update_certificate_context(context, user_certificate, platform_name)493 # Append badge info494 _update_badge_context(context, course, user)495 # Append site configuration overrides496 _update_configuration_context(context, configuration)497 # Add certificate header/footer data to current context498 context.update(get_certificate_header_context(is_secure=request.is_secure()))499 context.update(get_certificate_footer_context())500 # Append/Override the existing view context values with any course-specific static values from Advanced Settings501 context.update(course.cert_html_view_overrides)502 # Track certificate view events503 _track_certificate_events(request, context, course, user, user_certificate)504 # FINALLY, render appropriate certificate...

Full Screen

Full Screen

views.py

Source:views.py Github

copy

Full Screen

1"""URL handlers related to certificate handling by LMS"""2from datetime import datetime3import dogstats_wrapper as dog_stats_api4import json5import logging6from django.conf import settings7from django.contrib.auth.decorators import login_required8from django.contrib.auth.models import User9from django.http import HttpResponse, Http404, HttpResponseForbidden10from django.utils.translation import ugettext as _11from django.views.decorators.csrf import csrf_exempt12from django.views.decorators.http import require_POST13from capa.xqueue_interface import XQUEUE_METRIC_NAME14from certificates.models import (15 certificate_status_for_student,16 CertificateStatuses,17 GeneratedCertificate,18 ExampleCertificate,19 CertificateHtmlViewConfiguration20)21from certificates.queue import XQueueCertInterface22from edxmako.shortcuts import render_to_response23from xmodule.modulestore.django import modulestore24from opaque_keys import InvalidKeyError25from opaque_keys.edx.keys import CourseKey26from opaque_keys.edx.locations import SlashSeparatedCourseKey27from util.json_request import JsonResponse, JsonResponseBadRequest28from util.bad_request_rate_limiter import BadRequestRateLimiter29logger = logging.getLogger(__name__)30@csrf_exempt31def request_certificate(request):32 """Request the on-demand creation of a certificate for some user, course.33 A request doesn't imply a guarantee that such a creation will take place.34 We intentionally use the same machinery as is used for doing certification35 at the end of a course run, so that we can be sure users get graded and36 then if and only if they pass, do they get a certificate issued.37 """38 if request.method == "POST":39 if request.user.is_authenticated():40 xqci = XQueueCertInterface()41 username = request.user.username42 student = User.objects.get(username=username)43 course_key = SlashSeparatedCourseKey.from_deprecated_string(request.POST.get('course_id'))44 course = modulestore().get_course(course_key, depth=2)45 status = certificate_status_for_student(student, course_key)['status']46 if status in [CertificateStatuses.unavailable, CertificateStatuses.notpassing, CertificateStatuses.error]:47 log_msg = u'Grading and certification requested for user %s in course %s via /request_certificate call'48 logger.info(log_msg, username, course_key)49 status = xqci.add_cert(student, course_key, course=course)50 return HttpResponse(json.dumps({'add_status': status}), mimetype='application/json')51 return HttpResponse(json.dumps({'add_status': 'ERRORANONYMOUSUSER'}), mimetype='application/json')52@csrf_exempt53def update_certificate(request):54 """55 Will update GeneratedCertificate for a new certificate or56 modify an existing certificate entry.57 See models.py for a state diagram of certificate states58 This view should only ever be accessed by the xqueue server59 """60 status = CertificateStatuses61 if request.method == "POST":62 xqueue_body = json.loads(request.POST.get('xqueue_body'))63 xqueue_header = json.loads(request.POST.get('xqueue_header'))64 try:65 course_key = SlashSeparatedCourseKey.from_deprecated_string(xqueue_body['course_id'])66 cert = GeneratedCertificate.objects.get(67 user__username=xqueue_body['username'],68 course_id=course_key,69 key=xqueue_header['lms_key'])70 except GeneratedCertificate.DoesNotExist:71 logger.critical('Unable to lookup certificate\n'72 'xqueue_body: {0}\n'73 'xqueue_header: {1}'.format(74 xqueue_body, xqueue_header))75 return HttpResponse(json.dumps({76 'return_code': 1,77 'content': 'unable to lookup key'}),78 mimetype='application/json')79 if 'error' in xqueue_body:80 cert.status = status.error81 if 'error_reason' in xqueue_body:82 # Hopefully we will record a meaningful error83 # here if something bad happened during the84 # certificate generation process85 #86 # example:87 # (aamorm BerkeleyX/CS169.1x/2012_Fall)88 # <class 'simples3.bucket.S3Error'>:89 # HTTP error (reason=error(32, 'Broken pipe'), filename=None) :90 # certificate_agent.py:17591 cert.error_reason = xqueue_body['error_reason']92 else:93 if cert.status in [status.generating, status.regenerating]:94 cert.download_uuid = xqueue_body['download_uuid']95 cert.verify_uuid = xqueue_body['verify_uuid']96 cert.download_url = xqueue_body['url']97 cert.status = status.downloadable98 elif cert.status in [status.deleting]:99 cert.status = status.deleted100 else:101 logger.critical('Invalid state for cert update: {0}'.format(102 cert.status))103 return HttpResponse(104 json.dumps({105 'return_code': 1,106 'content': 'invalid cert status'107 }),108 mimetype='application/json'109 )110 dog_stats_api.increment(XQUEUE_METRIC_NAME, tags=[111 u'action:update_certificate',112 u'course_id:{}'.format(cert.course_id)113 ])114 cert.save()115 return HttpResponse(json.dumps({'return_code': 0}),116 mimetype='application/json')117@csrf_exempt118@require_POST119def update_example_certificate(request):120 """Callback from the XQueue that updates example certificates.121 Example certificates are used to verify that certificate122 generation is configured correctly for a course.123 Unlike other certificates, example certificates124 are not associated with a particular user or displayed125 to students.126 For this reason, we need a different end-point to update127 the status of generated example certificates.128 Arguments:129 request (HttpRequest)130 Returns:131 HttpResponse (200): Status was updated successfully.132 HttpResponse (400): Invalid parameters.133 HttpResponse (403): Rate limit exceeded for bad requests.134 HttpResponse (404): Invalid certificate identifier or access key.135 """136 logger.info(u"Received response for example certificate from XQueue.")137 rate_limiter = BadRequestRateLimiter()138 # Check the parameters and rate limits139 # If these are invalid, return an error response.140 if rate_limiter.is_rate_limit_exceeded(request):141 logger.info(u"Bad request rate limit exceeded for update example certificate end-point.")142 return HttpResponseForbidden("Rate limit exceeded")143 if 'xqueue_body' not in request.POST:144 logger.info(u"Missing parameter 'xqueue_body' for update example certificate end-point")145 rate_limiter.tick_bad_request_counter(request)146 return JsonResponseBadRequest("Parameter 'xqueue_body' is required.")147 if 'xqueue_header' not in request.POST:148 logger.info(u"Missing parameter 'xqueue_header' for update example certificate end-point")149 rate_limiter.tick_bad_request_counter(request)150 return JsonResponseBadRequest("Parameter 'xqueue_header' is required.")151 try:152 xqueue_body = json.loads(request.POST['xqueue_body'])153 xqueue_header = json.loads(request.POST['xqueue_header'])154 except (ValueError, TypeError):155 logger.info(u"Could not decode params to example certificate end-point as JSON.")156 rate_limiter.tick_bad_request_counter(request)157 return JsonResponseBadRequest("Parameters must be JSON-serialized.")158 # Attempt to retrieve the example certificate record159 # so we can update the status.160 try:161 uuid = xqueue_body.get('username')162 access_key = xqueue_header.get('lms_key')163 cert = ExampleCertificate.objects.get(uuid=uuid, access_key=access_key)164 except ExampleCertificate.DoesNotExist:165 # If we are unable to retrieve the record, it means the uuid or access key166 # were not valid. This most likely means that the request is NOT coming167 # from the XQueue. Return a 404 and increase the bad request counter168 # to protect against a DDOS attack.169 logger.info(u"Could not find example certificate with uuid '%s' and access key '%s'", uuid, access_key)170 rate_limiter.tick_bad_request_counter(request)171 raise Http404172 if 'error' in xqueue_body:173 # If an error occurs, save the error message so we can fix the issue.174 error_reason = xqueue_body.get('error_reason')175 cert.update_status(ExampleCertificate.STATUS_ERROR, error_reason=error_reason)176 logger.warning(177 (178 u"Error occurred during example certificate generation for uuid '%s'. "179 u"The error response was '%s'."180 ), uuid, error_reason181 )182 else:183 # If the certificate generated successfully, save the download URL184 # so we can display the example certificate.185 download_url = xqueue_body.get('url')186 if download_url is None:187 rate_limiter.tick_bad_request_counter(request)188 logger.warning(u"No download URL provided for example certificate with uuid '%s'.", uuid)189 return JsonResponseBadRequest(190 "Parameter 'download_url' is required for successfully generated certificates."191 )192 else:193 cert.update_status(ExampleCertificate.STATUS_SUCCESS, download_url=download_url)194 logger.info("Successfully updated example certificate with uuid '%s'.", uuid)195 # Let the XQueue know that we handled the response196 return JsonResponse({'return_code': 0})197# pylint: disable=too-many-statements, bad-continuation198@login_required199def render_html_view(request):200 """201 This view generates an HTML representation of the specified student's certificate202 If a certificate is not available, we display a "Sorry!" screen instead203 """204 # Initialize the template context and bootstrap with default values from configuration205 context = {}206 configuration = CertificateHtmlViewConfiguration.get_config()207 context = configuration.get('default', {})208 invalid_template_path = 'certificates/invalid.html'209 # Translators: This text is bound to the HTML 'title' element of the page and appears210 # in the browser title bar when a requested certificate is not found or recognized211 context['document_title'] = _("Invalid Certificate")212 # Feature Flag check213 if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):214 return render_to_response(invalid_template_path, context)215 course_id = request.GET.get('course', None)216 context['course'] = course_id217 if not course_id:218 return render_to_response(invalid_template_path, context)219 # Course Lookup220 try:221 course_key = CourseKey.from_string(course_id)222 except InvalidKeyError:223 return render_to_response(invalid_template_path, context)224 course = modulestore().get_course(course_key)225 if not course:226 return render_to_response(invalid_template_path, context)227 # Certificate Lookup228 try:229 certificate = GeneratedCertificate.objects.get(230 user=request.user,231 course_id=course_key232 )233 except GeneratedCertificate.DoesNotExist:234 return render_to_response(invalid_template_path, context)235 # Override the defaults with any mode-specific static values236 context.update(configuration.get(certificate.mode, {}))237 # Override further with any course-specific static values238 context.update(course.cert_html_view_overrides)239 # Populate dynamic output values using the course/certificate data loaded above240 user_fullname = request.user.profile.name241 platform_name = context.get('platform_name')242 context['accomplishment_copy_name'] = user_fullname243 context['accomplishment_copy_course_org'] = course.org244 context['accomplishment_copy_course_name'] = course.display_name245 context['certificate_id_number'] = certificate.verify_uuid246 context['certificate_verify_url'] = "{prefix}{uuid}{suffix}".format(247 prefix=context.get('certificate_verify_url_prefix'),248 uuid=certificate.verify_uuid,249 suffix=context.get('certificate_verify_url_suffix')250 )251 context['logo_alt'] = platform_name252 accd_course_org_html = '<span class="detail--xuniversity">{partner_name}</span>'.format(partner_name=course.org)253 accd_platform_name_html = '<span class="detail--company">{platform_name}</span>'.format(platform_name=platform_name)254 # Translators: This line appears on the certificate after the name of a course, and provides more255 # information about the organizations providing the course material to platform users256 context['accomplishment_copy_course_description'] = _('a course of study offered by {partner_name}, '257 'through {platform_name}.').format(258 partner_name=accd_course_org_html,259 platform_name=accd_platform_name_html260 )261 context['accomplishment_more_title'] = _("More Information About {user_name}'s Certificate:").format(262 user_name=user_fullname263 )264 # Translators: This line appears on the page just before the generation date for the certificate265 context['certificate_date_issued_title'] = _("Issued On:")266 # Translators: The format of the date includes the full name of the month267 context['certificate_date_issued'] = _('{month} {day}, {year}').format(268 month=certificate.modified_date.strftime("%B"),269 day=certificate.modified_date.day,270 year=certificate.modified_date.year271 )272 # Translators: The Certificate ID Number is an alphanumeric value unique to each individual certificate273 context['certificate_id_number_title'] = _('Certificate ID Number')274 context['certificate_info_title'] = _('About {platform_name} Certificates').format(275 platform_name=platform_name276 )277 # Translators: This text describes the purpose (and therefore, value) of a course certificate278 # 'verifying your identity' refers to the process for establishing the authenticity of the student279 context['certificate_info_description'] = _("{platform_name} acknowledges achievements through certificates, which "280 "are awarded for various activities {platform_name} students complete "281 "under the <a href='{tos_url}'>{platform_name} Honor Code</a>. Some "282 "certificates require completing additional steps, such as "283 "<a href='{verified_cert_url}'> verifying your identity</a>.").format(284 platform_name=platform_name,285 tos_url=context.get('company_tos_url'),286 verified_cert_url=context.get('company_verified_certificate_url')287 )288 # Translators: Certificate Types correspond to the different enrollment options available for a given course289 context['certificate_type_title'] = _('{certificate_type} Certfificate').format(290 certificate_type=context.get('certificate_type')291 )292 context['certificate_verify_title'] = _("How {platform_name} Validates Student Certificates").format(293 platform_name=platform_name294 )295 # Translators: This text describes the validation mechanism for a certificate file (known as GPG security)296 context['certificate_verify_description'] = _('Certificates issued by {platform_name} are signed by a gpg key so '297 'that they can be validated independently by anyone with the '298 '{platform_name} public key. For independent verification, '299 '{platform_name} uses what is called a '300 '"detached signature"&quot;".').format(platform_name=platform_name)301 context['certificate_verify_urltext'] = _("Validate this certificate for yourself")302 # Translators: This text describes (at a high level) the mission and charter the edX platform and organization303 context['company_about_description'] = _("{platform_name} offers interactive online classes and MOOCs from the "304 "world's best universities, including MIT, Harvard, Berkeley, University "305 "of Texas, and many others. {platform_name} is a non-profit online "306 "initiative created by founding partners Harvard and MIT.").format(307 platform_name=platform_name308 )309 context['company_about_title'] = _("About {platform_name}").format(platform_name=platform_name)310 context['company_about_urltext'] = _("Learn more about {platform_name}").format(platform_name=platform_name)311 context['company_courselist_urltext'] = _("Learn with {platform_name}").format(platform_name=platform_name)312 context['company_careers_urltext'] = _("Work at {platform_name}").format(platform_name=platform_name)313 context['company_contact_urltext'] = _("Contact {platform_name}").format(platform_name=platform_name)314 context['company_privacy_urltext'] = _("Privacy Policy")315 context['company_tos_urltext'] = _("Terms of Service &amp; Honor Code")316 # Translators: This text appears near the top of the certficate and describes the guarantee provided by edX317 context['document_banner'] = _("{platform_name} acknowledges the following student accomplishment").format(318 platform_name=platform_name319 )320 context['logo_subtitle'] = _("Certificate Validation")321 if certificate.mode == 'honor':322 # Translators: This text describes the 'Honor' course certificate type.323 context['certificate_type_description'] = _("An {cert_type} Certificate signifies that an {platform_name} "324 "learner has agreed to abide by {platform_name}'s honor code and "325 "completed all of the required tasks for this course under its "326 "guidelines.").format(327 cert_type=context.get('certificate_type'),328 platform_name=platform_name329 )330 elif certificate.mode == 'verified':331 # Translators: This text describes the 'ID Verified' course certificate type, which is a higher level of332 # verification offered by edX. This type of verification is useful for professional education/certifications333 context['certificate_type_description'] = _("An {cert_type} Certificate signifies that an {platform_name} "334 "learner has agreed to abide by {platform_name}'s honor code and "335 "completed all of the required tasks for this course under its "336 "guidelines, as well as having their photo ID checked to verify "337 "their identity.").format(338 cert_type=context.get('certificate_type'),339 platform_name=platform_name340 )341 elif certificate.mode == 'xseries':342 # Translators: This text describes the 'XSeries' course certificate type. An XSeries is a collection of343 # courses related to each other in a meaningful way, such as a specific topic or theme, or even an organization344 context['certificate_type_description'] = _("An {cert_type} Certificate demonstrates a high level of "345 "achievement in a program of study, and includes verification of "346 "the student's identity.").format(347 cert_type=context.get('certificate_type')348 )349 # Translators: This is the copyright line which appears at the bottom of the certificate page/screen350 context['copyright_text'] = _('&copy; {year} {platform_name}. All rights reserved.').format(351 year=datetime.now().year,352 platform_name=platform_name353 )354 # Translators: This text represents the verification of the certificate355 context['document_meta_description'] = _('This is a valid {platform_name} certificate for {user_name}, '356 'who participated in {partner_name} {course_number}').format(357 platform_name=platform_name,358 user_name=user_fullname,359 partner_name=course.org,360 course_number=course.number361 )362 # Translators: This text is bound to the HTML 'title' element of the page and appears in the browser title bar363 context['document_title'] = _("Valid {partner_name} {course_number} Certificate | {platform_name}").format(364 partner_name=course.org,365 course_number=course.number,366 platform_name=platform_name367 )368 # Translators: This text fragment appears after the student's name (displayed in a large font) on the certificate369 # screen. The text describes the accomplishment represented by the certificate information displayed to the user370 context['accomplishment_copy_description_full'] = _("successfully completed, received a passing grade, and was "371 "awarded a {platform_name} {certificate_type} "372 "Certificate of Completion in ").format(373 platform_name=platform_name,374 certificate_type=context.get("certificate_type")375 )...

Full Screen

Full Screen

package_binaries.py

Source:package_binaries.py Github

copy

Full Screen

1#!/usr/bin/env python2import argparse3import getnwisrelease4import getnwversion5import gzip6import os7import platform8import shutil9import sys10import tarfile11import zipfile12from subprocess import call13steps = ['nw', 'chromedriver', 'symbol', 'headers', 'others']14################################15# Parse command line args16parser = argparse.ArgumentParser(description='Package nw binaries.')17parser.add_argument('-p','--path', help='Where to find the binaries, like out/Release', required=False)18parser.add_argument('-a','--arch', help='target arch', required=False)19parser.add_argument('-m','--mode', help='package mode', required=False)20group = parser.add_mutually_exclusive_group()21group.add_argument('-s','--step', choices=steps, help='Execute specified step.', required=False)22group.add_argument('-n','--skip', choices=steps, help='Skip specified step.', required=False)23args = parser.parse_args()24################################25# Init variables.26binaries_location = None # .../out/Release27platform_name = None # win/linux/osx28arch = None # ia32/x6429step = None # nw/chromedriver/symbol30skip = None31nw_ver = None # x.xx32dist_dir = None # .../out/Release/dist33package_name = 'nwjs'34if args.mode == 'mas':35 package_name = 'nwjs-macappstore'36step = args.step37skip = args.skip38binaries_location = args.path39# If the binaries location is not given, calculate it from script related dir.40if binaries_location == None:41 binaries_location = os.path.join(os.path.dirname(__file__),42 os.pardir, os.pardir, os.pardir, 'out', 'Release')43if not os.path.isabs(binaries_location):44 binaries_location = os.path.join(os.getcwd(), binaries_location)45if not os.path.isdir(binaries_location):46 print 'Invalid path: ' + binaries_location47 exit(-1)48binaries_location = os.path.normpath(binaries_location)49dist_dir = os.path.join(binaries_location, 'dist')50print 'Working on ' + binaries_location51if sys.platform.startswith('linux'):52 platform_name = 'linux'53elif sys.platform in ('win32', 'cygwin'):54 platform_name = 'win'55elif sys.platform == 'darwin':56 platform_name = 'osx'57else:58 print 'Unsupported platform: ' + sys.platform59 exit(-1)60_arch = platform.architecture()[0]61if _arch == '64bit':62 arch = 'x64'63elif _arch == '32bit':64 arch = 'ia32'65else:66 print 'Unsupported arch: ' + _arch67 exit(-1)68if platform_name == 'win':69 arch = 'ia32'70if platform_name == 'osx':71 # detect output arch72 nw_bin = binaries_location + '/nwjs.app/Contents/MacOS/nwjs'73 import subprocess74 if 'i386' in subprocess.check_output(['file',nw_bin]):75 arch = 'ia32'76 else: # should be 'x86_64'77 arch = 'x64'78if args.arch != None:79 arch = args.arch80nw_ver = getnwversion.nw_version81if getnwisrelease.release == 0:82 nw_ver += getnwisrelease.postfix83################################84# Generate targets85#86# target example:87# {88# 'input' : [ 'nw', 'nw.pak', ... ]89# 'output' : 'nwjs-v0.9.2-linux-x64'90# 'compress' : 'tar.gz'91# 'folder' : True # Optional. More than 2 files will be put into a seprate folder92# # normally, if you want do to this for only 1 file, set this flag.93# }94def generate_target_nw(platform_name, arch, version):95 target = {}96 # Output97 target['output'] = ''.join([98 package_name, '-',99 'v', version,100 '-', platform_name,101 '-', arch])102 # Compress type103 if platform_name == 'linux':104 target['compress'] = 'tar.gz'105 else:106 target['compress'] = 'zip'107 # Input108 if platform_name == 'linux':109 target['input'] = [110 'credits.html',111 'libffmpegsumo.so',112 'nw.pak',113 'nwjc',114 'nw',115 'icudtl.dat',116 'locales',117 ]118 elif platform_name == 'win':119 target['input'] = [120 'd3dcompiler_47.dll',121 'ffmpegsumo.dll',122 'icudtl.dat',123 'libEGL.dll',124 'libGLESv2.dll',125 'pdf.dll',126 'nw.exe',127 'nw.pak',128 'locales',129 'nwjc.exe',130 'credits.html',131 ]132 elif platform_name == 'osx':133 target['input'] = [134 'nwjs.app',135 'nwjc',136 'credits.html',137 ]138 else:139 print 'Unsupported platform: ' + platform_name140 exit(-1)141 return target142def generate_target_chromedriver(platform_name, arch, version):143 if args.mode == 'mas':144 return generate_target_empty(platform_name, arch, version)145 target = {}146 # Output147 target['output'] = ''.join([148 'chromedriver-nw-',149 'v', version,150 '-', platform_name,151 '-', arch])152 # Compress type153 if platform_name == 'linux':154 target['compress'] = 'tar.gz'155 else:156 target['compress'] = 'zip'157 # Input158 if platform_name == 'win':159 target['input'] = ['chromedriver.exe']160 else:161 target['input'] = ['chromedriver']162 target['folder'] = True # always create a folder163 return target164def generate_target_symbols(platform_name, arch, version):165 target = {}166 target['output'] = ''.join([package_name, '-symbol-',167 'v', version,168 '-', platform_name,169 '-', arch])170 if platform_name == 'linux':171 target['compress'] = 'tar.gz'172 target['input'] = ['nw.breakpad.' + arch]173 target['folder'] = True174 elif platform_name == 'win':175 target['compress'] = None176 target['input'] = ['nw.sym.7z']177 target['output'] = ''.join([package_name, '-symbol-',178 'v', version,179 '-', platform_name,180 '-', arch, '.7z'])181 elif platform_name == 'osx':182 target['compress'] = 'zip'183 target['input'] = [184 'nwjs.breakpad.tar'185 ]186 target['folder'] = True187 else:188 print 'Unsupported platform: ' + platform_name189 exit(-1)190 return target191def generate_target_headers(platform_name, arch, version):192 # here, call make_nw_header tool to generate headers193 # then, move to binaries_location194 target = {}195 target['output'] = ''196 target['compress'] = None197 if platform_name == 'osx':198 target['input'] = []199 # here , call make-nw-headers.py to generate nw headers200 make_nw_header = os.path.join(os.path.dirname(__file__), \201 'make-nw-headers.py')202 print make_nw_header203 res = call(['python', make_nw_header])204 if res == 0:205 print 'nw-headers generated'206 nw_headers_name = 'nw-headers-v' + version + '.tar.gz'207 nw_headers_path = os.path.join(os.path.dirname(__file__), \208 os.pardir, 'tmp', nw_headers_name)209 if os.path.isfile(os.path.join(binaries_location, nw_headers_name)):210 os.remove(os.path.join(binaries_location, nw_headers_name))211 shutil.move(nw_headers_path, binaries_location)212 target['input'].append(nw_headers_name)213 else:214 #TODO, handle err215 print 'nw-headers generate failed'216 elif platform_name == 'win':217 target['input'] = []218 elif platform_name == 'linux':219 target['input'] = []220 else:221 print 'Unsupported platform: ' + platform_name222 exit(-1)223 return target224def generate_target_empty(platform_name, arch, version):225 target = {}226 target['output'] = ''227 target['compress'] = None228 if platform_name == 'win':229 target['input'] = []230 elif platform_name == 'linux' :231 target['input'] = []232 else:233 target['input'] = []234 return target235def generate_target_others(platform_name, arch, version):236 target = {}237 target['output'] = ''238 target['compress'] = None239 if platform_name == 'win':240 target['input'] = ['nw.exp', 'nw.lib']241 elif platform_name == 'linux' :242 target['input'] = []243 else:244 target['input'] = []245 return target246################################247# Make packages248def compress(from_dir, to_dir, fname, compress):249 from_dir = os.path.normpath(from_dir)250 to_dir = os.path.normpath(to_dir)251 _from = os.path.join(from_dir, fname)252 _to = os.path.join(to_dir, fname)253 if compress == 'zip':254 z = zipfile.ZipFile(_to + '.zip', 'w', compression=zipfile.ZIP_DEFLATED)255 if os.path.isdir(_from):256 for root, dirs, files in os.walk(_from):257 for f in files:258 _path = os.path.join(root, f)259 z.write(_path, _path.replace(from_dir+os.sep, ''))260 else:261 z.write(_from, fname)262 z.close()263 elif compress == 'tar.gz': # only for folders264 if not os.path.isdir(_from):265 print 'Will not create tar.gz for a single file: ' + _from266 exit(-1)267 with tarfile.open(_to + '.tar.gz', 'w:gz') as tar:268 tar.add(_from, arcname=os.path.basename(_from))269 elif compress == 'gz': # only for single file270 if os.path.isdir(_from):271 print 'Will not create gz for a folder: ' + _from272 exit(-1)273 f_in = open(_from, 'rb')274 f_out = gzip.open(_to + '.gz', 'wb')275 f_out.writelines(f_in)276 f_out.close()277 f_in.close()278 else:279 print 'Unsupported compression format: ' + compress280 exit(-1)281def make_packages(targets):282 # check file existance283 for t in targets:284 for f in t['input']:285 src = os.path.join(binaries_location, f)286 if not os.path.exists(src):287 print 'File does not exist: ', src288 exit(-1)289 # clear the output folder290 if os.path.exists(dist_dir):291 if not os.path.isdir(dist_dir):292 print 'Invalid path: ' + dist_dir293 exit(-1)294 else:295 shutil.rmtree(dist_dir)296 # now let's do it297 os.mkdir(dist_dir)298 for t in targets:299 if len(t['input']) == 0:300 continue301 if t['compress'] == None:302 for f in t['input']:303 src = os.path.join(binaries_location, f)304 if t['output'] != '':305 dest = os.path.join(dist_dir, t['output'])306 else:307 dest = os.path.join(dist_dir, f)308 print "Copying " + f309 shutil.copy(src, dest)310 elif (t.has_key('folder') and t['folder'] == True) or len(t['input']) > 1:311 print 'Making "' + t['output'] + '.' + t['compress'] + '"'312 # copy files into a folder then pack313 folder = os.path.join(dist_dir, t['output'])314 os.mkdir(folder)315 for f in t['input']:316 src = os.path.join(binaries_location, f)317 dest = os.path.join(folder, f)318 if os.path.isdir(src): # like nw.app319 shutil.copytree(src, dest)320 else:321 shutil.copy(src, dest)322 compress(dist_dir, dist_dir, t['output'], t['compress'])323 # remove temp folders324 shutil.rmtree(folder)325 else:326 # single file327 print 'Making "' + t['output'] + '.' + t['compress'] + '"'328 compress(binaries_location, dist_dir, t['input'][0], t['compress'])329# must be aligned with steps330generators = {}331generators['nw'] = generate_target_nw332generators['chromedriver'] = generate_target_chromedriver333generators['symbol'] = generate_target_symbols334generators['headers'] = generate_target_headers335generators['others'] = generate_target_others336################################337# Process targets338targets = []339for s in steps:340 if (step != None) and (s != step):341 continue342 if (skip != None) and (s == skip):343 continue344 targets.append(generators[s](platform_name, arch, nw_ver))345print 'Creating packages...'346make_packages(targets)...

Full Screen

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run molecule automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful